Bachelorarbeit Auswertung von XQuery – Anfragen an schemalose XML – Daten in einer relationalen Datenbank Eike Jan Menking Matrikelnr.: 2117234 9. März 2005 Universität Hannover Institut für Informationssysteme FG Datenbanksysteme Erstprüfer: Prof. Dr. Lipeck Zweitprüfer: Dr. Hans-Hermann Brüggemann Betreuer: Dipl.-Math. Sascha Klopp Erklärung Hiermit versichere ich, dass ich die vorliegende Bachelorarbeit selbstständig verfasst und keine anderen als die angegebenen Quellen und Hilfsmittel benutzt habe. Hannover, 9. März 2005 Eike Jan Menking Zusammenfassung Diese Arbeit befasst sich mit der Frage, wie XQuery–Anfragen an schemalose XML– Daten, die in einer relationalen Datenbank gespeichert wurden, effizient umgesetzt werden können. Der erste betrachtete Aspekt ist hierbei, eine Methode zu finden, wie hierarchische XML–Datenstrukturen auf flache relationale Datenbankstrukturen abgebildet werden können. Besondere Beachtung finden in dieser Arbeit XML–Daten, die keinem (gegebenen) Schema unterliegen, so dass die Struktur der Daten weder bei der Organisation der Datenbankstruktur noch zum Zeitpunkt der Speicherung vollständig bekannt ist. Hierzu werden Möglichkeiten untersucht, solchermaßen vorliegende Daten in Strukturen zu speichern, die hinreichend generisch sind. Hierbei wird Wert darauf gelegt, dass die Speicherung möglichst verlustfrei geschieht, um gespeicherte XML–Daten wieder herstellen zu können und Anfrageergebnisse, die an die XML–Sicht auf die Daten gestellt wurden, nicht durch Seiteneffekte, die durch Veränderung der XML–Struktur auftreten können, zu verfälschen. Außerdem soll die Speicherung möglichst effizient erfolgen. Der zweite Aspekt ist die Umsetzung von XQuery–Anfragen, die an die virtuelle Sicht auf die XML–Daten gestellt wurden, auf Anfragen an die relationale Sicht auf diese Daten umzusetzen. Auch hierbei wird darauf Wert gelegt, dass sowohl die Kompilierung der Anfrage als auch die kompilierte Anfrage selber möglichst effizient gestaltet wird. 3 Inhaltsverzeichnis 1 Einführung 1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 Gliederung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Grundlagen 2.1 Extensible Markup Language (XML) . . . . . 2.2 Klassifizierung von XML–Dokumenten . . . . 2.2.1 Dokumentzentrierte XML–Dokumente 2.2.2 Datenzentrierte XML–Dokumente . . . 2.3 XML Document Object Model (DOM) . . . . 2.4 XPath . . . . . . . . . . . . . . . . . . . . . . 2.5 XQuery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 XML in relationalen Datenbanken 3.1 Speicherung von XML als Zeichenkette . . . . . . . . . . . . . . . . 3.2 Abbildung von Dokumentstrukturen auf Datenbankstrukturen . . . 3.3 Abbildung des Document Object Model auf Datenbankstrukturen . 3.3.1 Entwicklung einer Datenbankstruktur . . . . . . . . . . . . . 3.3.2 Entwicklung eines Nummerierungsschemas aus dem Nested Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3.3 Eine sequentielle Betrachtung des Nested Set Model . . . . . 3.3.4 Zusammenfassung der Datenbankstruktur . . . . . . . . . . . . . . . . . . . . . . Set . . . . . . . . . 4 Umsetzung 4.1 Vorgehensweise und Einschränkungen . . . . . 4.2 Umsetzung der Datenbankstruktur . . . . . . 4.3 Umsetzung von Axis Steps in Location Paths . 4.4 Umsetzung von Location Paths . . . . . . . . 4.5 Funktionsaufrufe . . . . . . . . . . . . . . . . 4.6 FLWR–Ausdrücke in XQuery–Anfragen . . . . 4.6.1 Umsetzung der FOR–Klausel . . . . . 4.6.2 Umsetzung der LET–Klausel . . . . . . 4.6.3 Umsetzung der WHERE–Klausel . . . 4.6.4 Umsetzung der RETURN–Klausel . . . 4.6.5 Umsetzung des FLWR–Ausdrucks . . . . . . . . . . . . . . 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 3 5 6 6 7 8 9 9 10 12 15 16 19 21 22 29 37 39 41 41 43 43 50 53 55 55 56 57 58 61 Inhaltsverzeichnis 5 Implementation 5.1 Das Package xml.database . . . . . . . . . . . . . . 5.1.1 Die Klasse DatabaseAccess . . . . . . . . . . 5.1.2 Die Klasse DatabaseStructure . . . . . . . . 5.2 Das Package xml.dbstore . . . . . . . . . . . . . . . 5.2.1 Die Klasse NestedSetModelNode . . . . . . . 5.2.2 Die Klasse StorableDocument . . . . . . . . . 5.3 Das Package xml.xquery . . . . . . . . . . . . . . . . 5.3.1 Die Klassen XQueryLexer und XQueryParser 5.3.2 Die Klasse FlwrExpression . . . . . . . . . . 6 Kritik und Ausblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 64 64 65 65 65 66 68 68 72 74 2 1 Einführung 1.1 Motivation Bereits seit 1969 begann die Entwicklung des Konzepts des generic coding, dessen Ziel die Trennung des Informationsgehalts eines Dokuments von dessen äußerer Form ist. 1986 endete die bereits acht Jahre zuvor begonnene Weiterentwicklung einer auf GML ([GML60]) basierenden standardisierten Textbeschreibungssprache unter Leitung des American National Standards Institute (ANSI) und der International Organization for Standardization (ISO) mit der Veröffentlichung der Standard Generalized Markup Language (SGML) als ISO–Standards 8879 ([fSI86]). Mit SGML existiert eine standardisierte Methode, zusätzlich zur Gestaltung auch Inhalt und Struktur elektronischer Dokumente zu beschreiben. Formal beschriebene und standardisierte Sprachen erlauben standardisierte Schnittstellen und damit die Möglichkeit eines standardisierten Datenaustausches zwischen Applikationen und datenverarbeitenden Systemen. Durch die zunehmende Vernetzung von Rechnern und bereits bestehender Rechnernetze, ursprünglich überwiegend in Universitäten und anderen Forschungseinrichtungen anzutreffen, nahm auch der elektronische Austausch von Dokumenten zu. Mit dem Ziel, eine Sprache zu entwickeln, die die große Komplexität des SGML–Standards umgeht und dennoch einen standardisierten Austausch von Informationen zu ermöglichen, entwickelte Tim Berners-Lee 1990 an der European Organization for Nuclear Research (CERN) die Hypertext Markup Language (HTML) als eine Teilmenge von SGML. HTML ermöglicht eine einfache Beschreibung von Texten, Einbindung von Grafiken und Hypertextfunktionalität. Die daraufhin einsetzende Entwicklung von Client–Applikationen, die HTML verstehen und grafisch darstellen können (Browser) und die damit einhergehende Möglichkeit, komfortabel und schnell an elektronisch verfügbare Informationen zu gelangen, weckte schnell auch außerhalb der ursprünglichen Einrichtungen ein großes Interesse. Die relative Einfachheit der Sprache HTML gegenüber SGML sorgte schnell für eine breite Akzeptanz und weite Verbreitung von HTML. Der Nachteil war und ist, dass HTML vielmehr als Beschreibungssprache für das Aussehen und die Darstellung denn als Beschreibung des Inhalts und seiner Struktur dient und damit in seiner ursprünglichen Form nicht dem Prinzip des generic coding entspricht. Bemühungen, zu diesem ursprünglichen Prinzip zurückzukehren, scheiterten zunächst einerseits an den Grenzen, die die vorhandenen Leistungsmöglichkeiten von Soft- und Hardware dem Versuch setzten, diese an den sehr komplexen kompletten SGML–Standard anzupassen, andererseits an der rasant fortschreitenden Verbreitung und Akzeptanz von HTML. Das World Wide Web Consortium (W3C) beteiligte sich maßgeblich daran, eine Spra- 3 1 Einführung che zu schaffen, die die Mächtigkeit und Flexibilität von SGML sowie die Einfachheit und Akzeptanz von HTML vereint. 1998 veröffentlichte das W3C die erste Empfehlung ([AYBB+ 04]) einer solchen Sprache: die Extensible Markup Language (XML). XML ist wie HTML als eine Teilmenge von SGML spezifiziert und damit zu SGML aufwärtskompatibel. XML ist heute einer der wichtigsten, wenn nicht sogar der wichtigste Standard für den elektronischen Austausch von Informationen zwischen verschiedenen Applikationen, Systemen und Anwendern. XML stellt einen Satz von Richtlinien bereit, um Daten zu strukturieren. XML ist maschinen- sowie menschenlesbar, plattformunabhängig und unterstützt Internationalisierung durch Unicode. Moderne Applikationen stellen im zunehmenden Maße Im- und Exportmöglichkeiten von Daten im XML–Format bereit. Die Verbreitung von XML als universell einsetzbares Informationsmedium führt nicht zuletzt aufgrund seiner hohen Flexibilität dazu, dass zwar viele Daten in Form von XML vorliegen oder verfügbar gemacht werden können, aber anders als klassische SGML– Dokumente nicht mit einer expliziten Document Type Definition (DTD) oder anderen Beschreibungen der Datenstruktur wie XML–Schemas1 versehen sind oder werden können, aufgrund derer die Struktur der Daten erfasst werden kann. Die Vorteile, die XML hinsichtlich Kompatibilität, Einfachheit und Lesbarkeit bietet, wirft auch die Frage auf, wie insbesondere mit großen Datenbeständen am besten verfahren werden soll. Häufig liegen XML–Datenbestände vor allem aus Gründen der Lesbarkeit wie HTML–Dokumente in Form von Zeichenketten vor. Werden solche Datenbestände gespeichert, geschieht dies in der Regel durch Speicherung dieser Zeichenketten, was zu einem hohen zusätzlichen Anteil an Verwaltungsdaten führt. Um mit diesen Daten arbeiten zu können, bieten Programmierschnittstellen zu XML im Wesentlichen die Möglichkeit, sich zu entscheiden, die Datenbestände als Ganzes im potentiell knappen Arbeitsspeicher zu halten oder auf den Zeichenketten-Repräsentationen der XML–Daten zu arbeiten. Ersteres wird durch die Document Object Model (DOM)–API, letzteres durch die Simple API for XML (SAX) erreicht. Nachteilig sind dabei bei großen Datenmengen der hohe Arbeitsspeicherbedarf (DOM–API) der Daten bzw. die langen Zugriffszeiten (SAX) auf die Daten. Bereits 1970 stellte Dr. E. F. Codd in [Cod70] zum Zweck der Speicherung großer Datenmengen das Konzept relationaler Datenbankstrukturen vor. Basierend auf dieser Arbeit entwickelten sich die relationale Structured Query Language (SQL) und relationale Datenbank-Managementsysteme (RDBMS) wie Oracle oder DB2, die 1979 bzw. 1980 als erste kommerzielle RDBMS Marktreife erreichten. 1986 wurde SQL erstmals zu einem ANSI–Standard (ANSISQL) und einige Monate später ebenfalls zu einem ISO–Standard (ISOSQL). RDBMS sowie SQL wurden bis zu dem heutigen Tag weiterentwickelt, haben sich im Umgang mit großen Datenmengen hinsichtlich Persistenz und Effizienz von Anfragen bewährt und sind bis heute integraler Bestandteil vieler Anwendungen und aus der effizienten Verwaltung großer Datenmengen bis heute nicht wegzudenken. Mit der starken Verbreitung von XML und damit einer zunehmenden Verbreitung 1 Diese Schreibweise ist beabsichtigt. Die eigentlich korrekte Schreibweise "schemata" hat sich gegen die gebräuchliche amerikanische Variante "schemas" als Pluralform nicht durchsetzen können. 4 1 Einführung von hierarchischen Datenbeständen lässt sich eine Tendenz der Entwicklung von flachen relationalen Datenbankstrukturen hin zu objektrelationalen Datenbankstrukturen beobachten. Es drängt sich jedoch die Frage auf, inwiefern sich die bereits vorhandenen, erprobten und durch jahrzehntelange Entwicklung perfektionierten RDBMS dazu eignen, auch für die Speicherung dieser Art hierarchischer Daten eingesetzt zu werden. 1.2 Gliederung Kapitel 2 gibt einen Überblick über die in dieser Arbeit verwendeten Sprachen und Konzepte. Es werden Syntax und Semantik der Sprache XML sowie eine gängige Klassifizierung von XML–Dokumenten anhand ihres strukturellen Aufbaus erläutert. Die Sprache XQuery beziehungsweise ein für diese Arbeit relevantes Konstrukt dieser Sprache, der sogenannte FLWOR-Ausdruck, wird anhand eines Beispiels erläutert. Die Sprache XPath (eine Teilmenge von XQuery) wird aufgrund ihres komplexen Aufbaus sowie ihrer Funktionalität als die "eigentliche" Anfragesprache für XML–Daten separat anhand eines Beispiels erläutert. Das heute gängige und in dieser Arbeit häufig referenzierte Document Object Model als logische Repräsentation einzelner XML–Elemente wird kurz vorgestellt. Kapitel 3 beschreibt unterschiedliche Datenstrukturen, Indizierungsschemata und Vorgehensweisen, um XML–Daten in relationalen Datenbanken zu speichern. Es wird eine Datenbankstruktur und ein Indizierungsschema entwickelt, die dazu geeignet sind, beliebige XML–Daten schemaunabhängig, verlustfrei und strukturerhaltend abzubilden. Das Indizierungsschema wird weiterentwickelt, so dass es eine Grundlage für die effiziente Auswertung von Anfragen an die ursprünglich hierarchischen XML–Daten bildet. Hierauf basierend werden Grundlagen entwickelt, um die wesentlichen Anfragen an diese Daten umzusetzen. Kapitel 4 beschreibt die Umsetzung der entwickelten Modelle sowie Vorgehensweisen für die technische Umsetzung von XQuery–Anfragen. In diesem Zusammenhang wird der im Rahmen dieser Arbeit unterstützte Sprachumfang von XQuery geeignet eingeschränkt und näher erläutert. Kapitel 5 beschreibt die Implementierung der im vierten Kapitel vorgestellten Vorgehensweisen zur Umsetzung von XQuery–Anfragen. Es werden im Wesentlichen die einzelnen implementierten Klassen und Pakete anhand von UML–Klassendiagrammen und Beschreibungen ihrer Funktionsweise kurz vorgestellt. Einzelne spezielle Implementierungen von Methoden dieser Klassen werden anhand von Codebeispielen erläutert. Kapitel 6 gibt einen kritischen Überblick über die implizit und explizit getroffenen Einschränkung des unterstützen Umfangs der Sprachen XML und XQuery und versucht Möglichkeiten zu vermitteln, den unterstützten Sprachumfang zu erweitern. 5 2 Grundlagen In den folgenden Abschnitten sollen die wichtigsten Standards, auf die sich diese Arbeit bezieht, vorgestellt werden. Damit soll ein Überblick über XML und die daraus entstandenen begleitenden oder ergänzenden Technologien gegeben werden, die in dieser Arbeit angesprochen werden. 2.1 Extensible Markup Language (XML) Die Extensible Markup Language, kurz XML, hat sich als vereinfachte Teilmenge aus SGML entwickelt. In seiner Empfehlung von 1998 veröffentlichte das W3C die erste Ausgabe der XML–Spezifikation und standardisierte damit XML. Zum heutigen Zeitpunkt ist die dritte Ausgabe dieser Spezifikation die aktuellste Version. XML definiert in der Regel im Gegensatz zu anderen Auszeichnungssprachen wie HTML die grundsätzliche Struktur von Daten. Entwickelt wurde XML mit der Idee, Daten von ihrer Repräsentation zu trennen. Strukturelemente dienen damit der Unterteilung der Daten. Die Daten werden dabei streng hierarchisch organisiert. Ein beispielhaftes kurzes XML–Dokument könnte wie folgt aussehen: <?xml version="1.0"?> <telefonliste> <eintrag id="1"> <name>Mustermann</name> <vorname>Max</vorname> <vorwahl>0511</vorwahl> <nummer>1234567</nummer> </eintrag> <eintrag id="2"> <vorwahl /> <nummer>1234567</nummer> </eintrag> </telefonliste> Beispiel 2.1: Ein beispielhaftes XML–Dokument. Die XML–Deklaration <?xml version="1.0"?> spezifiziert die XML–Version (bis zum heutigen Tag immer 1.0) und gegebenenfalls weitere Verarbeitungshinweise sowie die verwendete Zeichenkodierung. 6 2 Grundlagen Die Namen der Strukturelemente (telefonliste, eintrag, name, ...) lassen sich mit geringen Einschränkungen frei wählen. Diese Elemente gliedern die Daten physisch mit Hilfe von Start- und passenden Endelementen, sogenannten Tags. Ein solches Element ist zum Beispiel <tag_name>...</tag_name>. Inhaltslose Elemente werden als <tag_name /> notiert. Elemente können andere Elemente enthalten. Die Tags schließen diese dabei ein, so dass hierarchische Strukturen entstehen. Ein XML–Dokument enthält immer genau ein Element auf der obersten Ebene der Hierarchie. Elemente können mit Attributen (Schlüssel–Wert–Paare wie id="1" versehen werden und dienen als Zusatzinformation für Elemente. XML bietet neben Attributen und Elementen weitere Möglichkeiten, Daten zu spezifizieren und XML–Dokumente zu strukturieren (CData–Sektionen, Verarbeitungsanweisungen, Kommentare etc.), deren ausführliche Beschreibung hier für das Grundverständnis nicht unbedingt erforderlich sind. Für eine vollständige Beschreibung sei hier ersatzweise auf die XML–Spezifikation [BPSM+ 04] verwiesen. XML–Dokumente können aufgrund ihrer geschachtelten Struktur als sortierte Bäume betrachtet werden. XML–Dokumente enthalten immer genau ein Element (auf oberster Ebene). Dieses Element wird dabei als der Wurzelknoten betrachtet, geschachtelte Elemente als Kinderknoten. Die Liste der direkten Nachfolgerknoten eines Knoten erhält die Reihenfolge, in der die entsprechenden Elemente in dem Dokument auftreten. Diese Reihenfolge wird auch als Dokumentenordnung bezeichnet, die bei XML erhalten wird. <telefonliste> <eintrag id=“1“> <eintrag id=“2“> <name> <vorname> <vorwahl> <nummer> Mustermann Max 0511 1234567 <vorwahl> <nummer> 1234567 Abbildung 2.1: Darstellung eines XML–Dokuments als Baum Erfüllt ein XML–Dokument die Vorgaben der Spezifikation, besteht es also insbesondere nur aus einem Strukturelement, das weitere Elemente enthalten kann, alle Starttags werden von den entsprechenden Endtags begleitet und die Strukturierung ist streng hierarchisch, spricht man von wohlgeformten XML–Dokumenten. XML–Parser können in der Regel nur wohlgeformte XML-Dokumente verarbeiten. 2.2 Klassifizierung von XML–Dokumenten XML ist ein universell einsetzbares Format zur Speicherung von Daten, wobei es besonders geeignet für die Speicherung von stark strukturierten Daten geeignet ist. XML 7 2 Grundlagen kann jedoch auch dafür eingesetzt werden, anstelle stark strukturierten Daten ganze Textdokumente, in denen Strukturelemente weniger der Strukturierung von Daten als vielmehr der Gestaltung der Textdokumente (Betonungs- oder Schriftgrößenparameter etc.) dienen, darzustellen. XML-Dokumente können, je nach Art und Struktur der Daten, die sie darstellen, in datenzentrierte (data-centric) und dokumentzentrierte (document-centric) XML-Dokumente unterschieden werden. Mischformen aus diesen beiden Charakterisierungen werden auch als semistrukturiert (semi-structured) bezeichnet. Ein häufig genanntes Beispiel für semistrukturierte Daten ist eine Anreisebeschreibung, die neben stark strukturierten Daten wie Straße, Hausnummer, Ort usw. auch über Freitexte verfügen kann, wie beispielsweise eine genauere Erläuterung mit Textcharakter. 2.2.1 Dokumentzentrierte XML–Dokumente XML kann zur (darstellungsbezogenen) Auszeichnung von Teilen von Dokumenten oder als Speicherformat für Textdokumente verwendet werden. Ein Beispiel hierfür ist XHTML. HTML dient der Beschreibung und der Darstellung des Inhaltes eines elektronischen Dokuments, wobei nicht die Strukturierung von abstrakten Wissen im Vordergrund steht. Damit können HTML-Dokumente nur schwer als Datenquelle verwendet werden. XHTML ist der Versuch, HTML in gültiges XML zu überführen1 , um HTML auch mit XML-Parsern verarbeiten zu können. Aber auch die Überführung von HTML in XML ändert nicht, dass die Dokumente in erster Linie gestaltete Texte anstatt von Darstellungsanweisungen freie Daten beinhalten. Darstellungsbezogene HTML-Elemente wie <b>Fettdruck</b> werden in XHTML zwar in inhaltlich bezogene XML-Elemente (<strong>Betonter Text</strong>) umgewandelt, aber sie strukturieren nach wie vor keine Daten, sondern zeichnen die Darstellung von Textdokumenten aus. Solche Daten werden neben "dokumentzentriert" auch als Markup Data bezeichnet. Die genaue Wiederherstellbarkeit eines solchen Dokuments ist in der Regel erforderlich. Anfragen an XML-Dokumente, deren Charakter dokumentzentriert ist, werden häufig nicht an besonders spezifisch strukturierte Daten wie Listen oder Tabellen gestellt, da diese meist als Teil des Dokumenteninhalts und nicht als separate Datenquellen vorliegen. Anfragen an diese Art von Dokumenten sind häufig inhaltsbezogen und erfordern wenigstens eine Indizierung von Volltexten. Information Retrieval ([Fer03]) ist ein Teilgebiet der Dokumentationswissenschaften, das sich mit der computergestützten inhaltsorientierten Informationsgewinnung aus (insbesondere textuellen) Datenbeständen beschäftigt. Im Bereich des Information Retrieval wurden Verfahren und spezielle Indizierungen von Volltexten entwickelt, um Anfrage auf Volltexten umzusetzen ([Kur04]). Häufig werden bei dieser Art der Speicherung verschiedene Verfahren der Indizierung parallel eingesetzt, wie Wortstammindizierungen, exakte Indizierung, phonetische Indizierung etc. 1 HTML und XML verfügen zwar über eine ganz ähnliche Syntax insbesondere hinsichtlich ihrer Strukturelemente, aber HTML lässt anders als XML auch Starttags zu, die nicht von einem Endtag begleitet werden, sogenannte Standalone–Elemente 8 2 Grundlagen 2.2.2 Datenzentrierte XML–Dokumente In datenzentrierten Dokumenten strukturieren XML–Elemente die Daten. Daten, die durch gleichartige Elemente strukturiert werden, können häufig einem gemeinsamen Datentyp wie Ganzzahlen oder Datumswerte zugeordnet werden. Die Darstellung solcher Daten ist für das XML–Dokument irrelevant. Die Daten an sich sind die Informationsträger, Konvertierungen in andere Formate würden den Informationsgehalt der Daten in der Regel nicht verändern. Eine genaue Wiederherstellbarkeit des Dokuments ist häufig nicht in dem Maße relevant wie die Wiederherstellbarkeit der Daten an sich. Anfragen an XML–Dokumente, deren Struktur diesem Charakter entspricht, können die gleichartige Datenstruktur homogener Elemente berücksichtigen und basieren in der Regel auf Vergleichs- oder sonstigen mathematischen Operationen wie Summen-, Maximal- oder Durchschnittswertbildung sowie Zeichenkettenoperationen wie die Berechnung von Teilzeichenketten usw. Typisierungen der Daten sind häufig möglich und können damit diese Anfragen erleichtern oder effizienter gestalten. Eine streng hierarchische Organisation von Daten ermöglicht schnelle Such- und Sortiermöglichkeiten der Daten. 2.3 XML Document Object Model (DOM) Um standardisierte Vorgehensweisen im Umgang mit XML–Dokumenten zu ermöglichen, haben sich mit XML auch verschiedene Application Programming Interfaces (API’s) herausgebildet. Die beiden bekanntesten sind die Simple API for XML (SAX), die durch Mitglieder der xml-dev–Mailingliste entwickelt wurde, und die Document Object Model (DOM)–API ([HHW+ 04]), eine durch das W3C standardisierte API unter anderem für HTML und XML. SAX ist eine auf seriellem ereignisbasiertem Parsen beruhende Schnittstelle für XML– Parser. Sie verarbeitet den Zeichenkettenstrom, in dem ein XML–Dokument notiert ist. Parser, die XML–Dokumente nicht mit dem Ziel verarbeiten, geändert zu werden, implementieren häufig diese Schnittstelle. Die DOM–API bildet die Baumstruktur der XML–Elemente des Dokuments ab. Diese Abbildung erfolgt objektorientiert und bietet Operationen zur Navigation in diesen Baumstrukturen und Manipulation der Objekte. Für einzelne Programmiersprachen sind optimierte Implementierungen (z. B. für Java JDOM, Java 1.4 und DOM4J) verfügbar. Zentraler Bestandteil der DOM–API ist das Interface Node. Diese Interface implementiert den Zugriff auf die Informationen eines Knoten der XML–Baumstruktur. Es bedient sich dabei der W3C–Empfehlung des Information Set ([CT04]), das einen Knoten als eine Informationseinheit definiert, die je nach Knotentyp über verschiedene spezifische Eigenschaften, jedoch immer über Eigenschaften ihrer Funktion als Knoten innerhalb der Baumstruktur verfügen. Insbesondere kennt jeder Knoten seinen linken und rechten Nachbarn, seinen Elternknoten, die geordnete Liste seiner Nachfolgerknoten, seinen Knotentyp und das Dokument, zu dem sie gehören. Knotentypen werde unterschieden nach der speziellen Funktion des Knotens, zum Beispiel comment node (Kommentarkno- 9 2 Grundlagen ten), text node (Textknoten), element node (Strukturelementknoten) oder attribute node (Attributknoten). #document <telefonliste> #text <vorwahl> #text <eintrag> #text #text <nummer> #text #attribute #text Abbildung 2.2: DOM–Repräsentation eines XML–Dokuments In Abbildung 2.2 ist ein Teil des XML–Dokuments aus Beispiel 2.1 entsprechend der baumartigen DOM–Sicht auf das XML–Dokument dargestellt2 . Auffallend dabei ist, dass der erste Kinderknoten des Elements <telefonliste> ein Textknoten ist. Der Grund hierfür ist, dass tatsächlich auf den <telefonliste>–Knoten ein Zeilenumbruch und einige Tabulator- oder Leerzeichen (sog. whitespaces) folgen, bevor das <eintrag>– Element folgt. 2.4 XPath XPath ist aus der Idee heraus entstanden, auf eine standardisierte, einfache und effiziente Weise an Informationen aus XML–Dokumenten zu gelangen. 1999 wurde die W3C– Empfehlung der ersten XPath–Version ([CD99]) veröffentlicht. Derzeit befindet sich eine zweite Version von XPath im Working Draft ([BBC+ 04]). Das Hauptziel von XPath ist die Adressierung von Teilen von XML–Dokumenten. XPath bedient sich dabei der abstrakten Sicht auf XML–Dokumente als Bäume, wie sie auch durch das Document Object Model modelliert wird. Der Sprachstandard XPath ist nicht mit XML verwandt. Die Syntax von XPath, genauer gesagt die Syntax einer wichtigen Teilmenge dieses Standard, der Location Paths, ähnelt der Syntax zur Navigation durch UNIX–basierte Datei- und Ordnerstrukturen oder von URL’s3 . Das hauptsächlich verwendete Konstrukt von XPath sind die Location Paths, die zu Node Sets (duplikatfreie Sammlungen von Knoten) ausgewertet werden. Die Auswertung eines Location Path basiert dabei immer auf einem sog. Kontext. Der Kontext besteht 2 Die Darstellung ist unvollständig. Eine vollständige Darstellung des Dokuments würde hier unnötig viel Platz verbrauchen 3 Diesem Umstand verdankt XPath seinen Namen 10 2 Grundlagen im Wesentlichen aus einem Kontext–Knoten, der Kontext–Position und -Größe, einer Menge von gebundenen Variablen und einer verfügbaren Funktionsbibliothek. /telefonliste/child::eintrag[./name = ’Mustermann’]/nummer Beispiel 2.2: Ein beispielhafter Location Path. Beispiel 2.2 zeigt einen Location Path. Dieser Pfad beginnt mit einem "/", dem Trennzeichen für die einzelnen Schritte (die so genannten Axis Steps), aus denen ein Location Path aufgebaut ist, und bezieht sich damit auf den Wurzelknoten eines XML– Dokuments. Eine Einleitung durch "//" würde sich auf alle Elemente eines XML– Dokuments beziehen. Ein Location Path besteht aus einer Aneinanderreihung von Axis Steps, die den Pfad von einem Kontextknoten (häufig dem Wurzelknoten) aus zu einem Blatt oder einem Teilbaum beschreiben. Die Schritte bewegen sich dabei entlang so genannter Achsen. Eine Achse aus Sicht von XPath kann vorwärts- oder rückwärtsgerichtet sein. Insgesamt gibt es fünf vorwärts- und vier rückwärts gerichtete Achsen sowie drei spezielle Achsen, die im Folgenden kurz erläutert werden sollen. Eine vorwärts gerichtete Achse gelangt immer von einem aktuellen Kontextknoten aus zu dessen nachfolgenden Knoten, wobei "nachfolgend" im Sinne von "in Dokumentenordnung folgend" zu verstehen ist. Die Achse child wählt die direkten Kinderknoten aus. descendant wählt zusätzlich alle Nachfahren eines Knoten mit aus. following wählt alle Knoten aus, die in Dokumentenordnung auf den aktuellen Knoten folgen. descendant oder following schließen in ihre Auswahl den aktuellen Knoten durch den Zusatz von -or-self mit ein. Eine rückwärtsgerichtete Achse gelangt entsprechend zu vorhergehenden Knoten. Die Achse parent beinhaltet den Elternknoten des aktuellen Knoten, die Achse preceding alle in Dokumentenordnung vorhergehenden Knoten und ancestor den Eltern- und jeden Vorfahren des aktuellen Knoten. Wie bei vorwärts gerichteten Achsen schließt die Angabe -or-self bei ancestor und preceding den aktuellen Knoten mit ein. self ist eine besondere Achse, die den aktuellen Knoten selbst bezeichnet und insofern nicht gerichtet ist. attribute und namespace bezeichnen die Attributknoten des aktuellen Knoten, die zu anderen Knoten des XML–Baums eine Sonderrolle einnehmen bzw. den Namespace des aktuellen Knoten. Ein vorwärts gerichteter Axis Step wählt durch Angabe eines Elementnamen (in diesem Fall an erster Stelle telefonliste) diejenigen Kinderknoten aus, deren Tagname diesem entspricht. Dies ist eine abgekürzte Form der eigentlichen Axis Steps, die allerdings sehr häufig verwendet wird und deswegen an dieser Stelle erwähnt werden soll. Ausführliche Axis Steps geben die Achse explizit an (child::eintrag) und trennen den Filter für die Auswahl des Knotentyps durch zwei Doppelpunkte "::" von der Achse. Die Auswahl einer Menge von Elementen kann durch zusätzliche Prädikate, die in eckigen Klammern angegeben werden, eingeschränkt werden ("[./name = ’Mustermann’]"). In diesem Fall handelt es sich bei dem Prädikat um eine Vergleichsoperation zwischen einem weiteren Location Path bzw. dem Textinhalt des Elements "name" und der Zeichenkette 11 2 Grundlagen ancestor preceding following self descendant Abbildung 2.3: Die wichtigsten Achsen in einem DOM–Baum "Mustermann". Der Location Path bezieht sich durch das Zeichen "." auf den aktuellen Kontext, der für dieses Prädikat aus allen Knoten besteht, die durch die vorhergehenden Axis Steps /telefonliste/child::eintrag ausgewählt wurden. Nur unter der Voraussetzung, dass ein direkter Nachfolgerknoten mit dem Elementnamen "name" über einen Inhalt verfügt, der mit der Zeichenkette "Mustermann" übereinstimmt, wird der Knoten mit in die Auswahl genommen. Dieses Beispiel wählt in diesem Fall das nummer - Element im ersten eintrag aus. 2.5 XQuery XQuery ist das Ergebnis der Konsolidierung verschiedener Entwicklungen zur Anfrage von strukturierten und semi-strukturierten XML–Daten mit dem Ziel, die Möglichkeiten relationaler Anfragesprachen mit der Effizienz von XPath zur Navigation und Auswahl von XML–Elementmengen aus XML–Dokumenten bzw. -Datenbanken zu kombinieren. In Anlehnung an die SELECT ... WHERE–Konstrukte aus SQL wurde XML-QL entwickelt, einer Anfragesprache, die Möglichkeiten zur Variablenbindung vorsieht und auf musterbasierten Konstrukten zum Matchen von XML–Elementen beruht ([DFF+ 98]). Eine andere Hauptrichtung bei der Entwicklung dieser Sprachen repräsentiert XQL, eine mit XPath verwandte, pfadbasierte Anfragesprache ([RDF+ 99]). Je nach Anwendungsfall, Struktur der Daten oder Anforderungen an die Ergebnismenge und dessen Repräsentation konnten in den unterschiedlichen Ansätzen und Implementierungen früher XML–Anfragesprachen verschiedene Anfragen unterschiedlich formuliert und ausgewertet werden. Mit Quilt ([RDF+ 99]) wurden Möglichkeiten verschiedener Anfragesprachen 12 2 Grundlagen in einer einheitlichen Sprache zusammengefasst. Mit der Weiterentwicklung von Quilt erreichte XQuery [CD99] als Working Draft des W3C die Vorstufe zu einem offiziellen Standard. Ein grundlegendes Konstrukt von XQuery sind FLWOR (For-Let-Where-OrderByReturn)–Ausdrücke, die Anfragemöglichkeiten von SQL durch Nachbildung der SELECT - FROM - WHERE - ORDER BY–Konstrukte bereitstellt. Die Auswahl und Filterung von angefragten XML–Elementmengen und der Ergebnismenge beruht dabei nicht unwesentlich auf XPath–Ausdrücken. <ansprechpartnerliste> { for $a in fn:doc("beispiel.xml")//eintrag let $b = $a/descendant-or-self::* where $a/name != ’’ order by $a/name return <ansprechpartner> {$b} </ansprechpartner> } </ansprechpartnerliste> Beispiel 2.3: Eine beispielhafte XQuery–Abfrage. XQuery stellt Variablen zur Verfügung. Variablen (gekennzeichnet durch ein vorangestelltes $-Symbol) können Knotenmengen zugewiesen werden, die wie jede andere Knotenmenge wieder über Location Paths abfragbar sind ($a/name). Zeichenketten- und Zahlenwertzuweisungen sind ebenfalls möglich. Variablenzuweisungen werden implizit durch For–Schleifen oder explizit durch Let–Anweisungen vorgenommen. Das Beispiel 2.3 zeigt eine typische XQuery–Anfrage, anhand derer sich der Aufbau und die Möglichkeiten von XQuery, insbesondere von FLWOR–Ausdrücken, beispielhaft demonstrieren lassen. Als beispiel.xml–Dokument soll diesem Beispiel das im Abschnitt über XML verwendete Beispiel 2.1 dienen. Das for–Konstrukt wählt hier alle eintrag–Elemente des XML–Dokuments aus (durch den abgekürzten Location Path "//", welcher descendant-or-self ausgehend vom Wurzelknoten des XML–Dokuments "beispiel.xml"). Die so definierte For–Schleife iteriert über alle Knoten der Knotenmenge, die durch den Location Path fn:doc("beispiel.xml")//eintrag ausgewählt werden. In der For– Klausel wird der Variablen $a in jedem Iterationsschritt der For–Schleife ein Knoten der ausgewählten Menge zugewiesen und ist innerhalb eines Iterationsschritts sichtbar. Das let–Konstrukt weist einer neuen Variable, die innerhalb eines Schleifendurchlaufs sichtbar ist, eine Knotenmenge zu, die durch einen Location Path bestimmt wird. Dieser kann dabei völlig unabhängig von der Laufvariablen $a sein oder auch an dem durch $a beschriebenen Knoten ansetzen. 13 2 Grundlagen Das where–Konstrukt schränkt im Beispiel 2.3 die Ergebnismenge weiter ein auf Elemente, die als Kindelement ein Element name enthalten, dessen Inhalt von einer leeren Zeichenkette verschieden ist. Im Allgemeinen werden von where–Klauseln alle gängingen Vergleichsoperationen (Kleiner-Größer-Beziehungen und Gleichheitsbeziehung) von Zeichenketten und numerischen Werten unterstützt. order by ordnet die Ergebnismenge nach dem Element name. Das return–Konstrukt <ansprechpartner>{$b}</ansprechpartner> bietet die Möglichkeit, neue XML–Dokumente zu erzeugen. <ansprechpartnerliste> <ansprechpartner> <eintrag id="1"> <name>Mustermann</name> <vorname>Max</vorname> <vorwahl>0511</vorwahl> <nummer>1234567</nummer> </eintrag> </ansprechpartner> </ansprechpartnerliste> Die Auswertung dieses XQuery–Ausdrucks ergibt ein neues XML–Dokument, das eine Liste von ansprechpartner–Elementen enthält, von denen jeder eine Kopie eines eintrag–Elements als Kindelement enthält. 14 3 Methoden zur Speicherung von XML in relationalen Datenbanken Die verwendete Methodik, wie die hierarchische Struktur von XML–Daten und die Daten selber am effizientesten in relationalen Speicherstrukturen abgelegt werden, hängt im Wesentlichen davon ab, welche Anforderungen einerseits an die Strukturerhaltung und Wiederherstellbarkeit des originalen Dokuments gestellt werden und andererseits, in welchem Maße die Daten strukturiert bzw. strukturierbar sind. XML–Elemente in einem XML–Dokument bewahren ihre Dokumentenordnung, da XML nicht nur für stark abstrahiertes Wissen eingesetzt werden kann, sondern auch für die Auszeichnung von Dokumenten (Volltexte) und Dokumentenbereichen (Kapitel in Büchern, Abschnitte in Texten) und Dokumententeilen (Fettdruck, andere Schriftfamilie oder -größe etc.). Eine Vernachlässigung der Dokumentenordnung würde hier den Informationsgehalt der Daten verringern oder gänzlich unbrauchbar machen. Stark textlich orientierte XML–Daten (dokumentzentrierte XML–Dokumente) sind häufig nicht oder kaum einheitlich strukturierbar. Anfragen an solche Daten sind eher semantisch orientierte Anfragen nach Worthäufigkeit, Wortstammähnlichkeiten und andere, durch das Gebiet des Information Retrieval behandelte Textmetriken. Datenzentrierte und damit oft typisiert strukturierte oder strukturierbare XML– Dokumente dagegen verlieren ihren Informationsgehalt häufig auch dann nicht oder nicht wesentlich, wenn ihre Dokumentenordnung verlorenginge. Eine genaue Wiederherstellbarkeit der XML–Dokumente nach einer Speicherung in relationalen Datenbankstrukturen ist unter Umständen vernachlässigbar. Dafür können diese Daten auch typisiert und damit effizienter gespeichert werden. Anfragen können unter Umständen typbezogen und damit effizienter umgesetzt werden. Diese Unterschiede in Struktur und Inhalt von Daten sowie Anforderungen an Wiederherstellbarkeit und Ergebnis haben unter anderen drei verbreitete Gruppen von Methoden hervorgebracht, um XML–Daten in relationalen Datenbankstrukturen abzuspeichern. Eine Gruppe speziell zum Speichern dokumentzentrierter XML–Dokumente versucht, XML–Dokumente oder einzelne Elemente in ihrer kompletten Zeichenkettenrepräsentation abzulegen. Eine zweite Gruppe versucht, die Baumstruktur der auftretenden Tags möglichst datenzentrierter und strukturierter XML–Dokumente auf relationale Datenbankstrukturen abzubilden. Informationen des Information Set der XML-Elemente werden nicht oder nicht hinreichend abgebildet und eine exakte Wiederherstellung des zugrundeliegenden XML-Dokuments ist unter Umständen nicht oder nur eingeschränkt möglich. 15 3 XML in relationalen Datenbanken Dafür können Typisierungen von homogenen Datenbeständen unter Umständen berücksichtigt werden. Eine dritte Gruppe versucht, ähnlich zur zweiten Gruppe, die Baumstruktur von XML–Dokumenten abzubilden, allerdings unter der Betrachtung der XML–Elemente als Information Sets. Diese Gruppe bedient sich dabei des Document Object Models (DOM) oder einer spezifischen Ableitung dieses Modells eines XML–Knoten als Informationseinheit. DOM–Objekte verfügen über jede Information eines XML–Knoten, so dass eine Wiederherstellung des originalen Dokuments nach einer Speicherung vollständig möglich ist. Mischformen einzelner Methoden sind durchaus möglich und nicht selten. In den folgenden Abschnitten sollen einige gängige Methoden der drei Gruppen erläutert werden. Die letztgenannten Methoden, die die DOM–Informationen berücksichtigen bzw. zusätzliche Informationen, die größtenteils dem sog. Nested Sets Model zugrundeliegen, werden dabei ausführlicher behandelt, da diese Methoden die Grundlage der Implementierung zu dieser Arbeit bilden. 3.1 Speicherung von XML–Dokumenten oder -Elementen in Form von Zeichenketten Dokumentzentrierte XML–Dokumente lassen häufig eine Strukturierung von Daten nur eingeschränkt oder nur unter großem Aufwand zu. Häufig ist eine starke Strukturierung in Form von Hierarchisierung der Daten auch gar nicht gewünscht oder sinnvoll. Das folgende Beispiel soll solch ein Dokument beispielhaft zeigen. <?xml version="1.0"?> <buch> <kapitel nr="1"> XML steht für <kursiv>Extensible Markup Language</kursiv> und bezeichnet ein Dokumentenformat zur Darstellung von strukturierten und semistrukturieren Daten. </kapitel> <kapitel nr="2"> Datenbanken werden eingesetzt, um große Datenmengen <fett>sicher</fett> und <unterstrichen>effizient</unterstrichen> anfragen zu können. </kapitel> </buch> Beispiel 3.1: Ein beispielhaftes dokumentzentriertes XML–Dokument. Dies ist ein typisches Beispiel für semistrukturierte Daten. Die Strukturelemente <buch> und <kapitel> strukturieren eine abstrakte Repräsentation eines Buches und dessen 16 3 XML in relationalen Datenbanken logische Einheiten in einer Folge von XML–Elementen. Diese Strukturelemente stellen einen datenzentrierten Anteil an XML–Elementen dar. Die Inhalte dieser Elemente, die Texte der Kapitel, enthalten ihrerseits aber auch XML–Elemente (<kursiv>, <unterstrichen> etc.), die im Gegensatz zu strukturierenden Elementen nicht der Strukturierung von Daten, sondern der Auszeichnung von Textbausteinen dienen. Anfragen an diese Form von XML–Dokumenten haben häufig einen anderen Charakter als Anfragen an abstrahierte Daten. Häufig repräsentieren diese XML–Dokumente Daten mit Dokumentcharakter, so wie XHTML-Dokumente. Anfragen werden speziell an den Gehalt einer Information, nicht an die Information selber gestellt. Vergleicht man die Struktur des XML–Dokuments aus Beispiel 3.1 mit den im Abschnitt über XML bereits verwendeten Beispiel 2.1 der Telefonliste, stellt man schnell fest, dass die Telefonliste eine reine Sammlung von Fakten bzw. abstrahierter Information enthält. Anfragen an diese Telefonliste beziehen sich im Wesentlichen konkret auf diese abstrahierten Informationen und die hierarchische Sicht auf die Daten, das Spektrum sinnvoller Anfragen lässt sich dabei im Wesentlichen durch XQuery abdecken. Im Gegensatz dazu werden Anfragen an dieses Dokument und die streng hierarchische Sicht der Daten eher selten auftreten, denn eine Anfrage aller mit <kursiv> ausgezeichneten Elemente wird hier in der Regel weniger sinnvoll erscheinen als eine Anfrage, welche Kapitel das Thema "Datenbank" behandeln. Eine Veränderung oder der Verlust der Wiederherstellbarkeit dieses Dokuments könnte unter Umständen zu einem (teilweisen) Verlust der Information führen. Eine Änderung der Dokumentreihenfolge bei einer Wiederherstellung aus einem anderen Speicherformat könnte zum Beispiel zu diesem Ergebnis führen1 : <?xml version="1.0"?> <buch> <kapitel nr="2"> <xml:text> und </xml:text> <xml:text> anfragen zu können.</xml:text> <xml:text>Datenbanken werden eingesetzt, um große Datenmengen </xml:text> <fett>sicher</fett> <unterstrichen>effizient</unterstrichen> </kapitel> <kapitel nr="1"> <kursiv>Extensible Markup Language</kursiv> <xml:text> und bezeichnet ein Dokumentenformat zur Darstellung von strukturierten und semistrukturieren Daten.</xml:text> <xml:text>XML steht für </xml:text> </kapitel> </buch> 1 Die <xml:text>–Elemente entstammen dem Umstand, dass jeder Textinhalt eines XML–Dokuments der Inhalt eines Textknoten ist, auch wenn dies so nicht explizit ausgezeichnet werden muss. 17 3 XML in relationalen Datenbanken Der Sinn der dokumentenzentrierten Anteile wurde durch eine Umsortierung der Kindelemente nach Elementtypen völlig entstellt und der Informationsgehalt ging dabei verloren. Nur die Umsortierung der strukturierenden <kapitel> - Elemente wirken nicht sinnentstellend. In solchen Fällen ist eine (möglichst originalgetreue) Wiederherstellbarkeit zumindest von ganzen XML-Teildokumenten unbedingt erforderlich, wohingegen ein Umsortierung der Kindelemente der Telefonliste den Informationsgehalt der Liste im wesentlichen unberührt ließe. Die Speicherung von XML-Teildokumenten wird durch Datenbanken durch ihre Bereitstellung hierfür einsetzbarer Datentypen unterstützt. Neben der Unterstützung für Speicherung von BLOB-Objekten im Allgemeinen hat sich mit der Einführung des Datentyps XMLType für SQL, beschrieben in Teil 14 des Standards SQL/XML ([fSI03]), ein Standard durchgesetzt, der besonders für die Speicherung von XML-Elementen geeignet ist. Unter anderem stellen RDBMS mit diesem Datentyp Methoden zur Prüfung der Wohlgeformtheit und Validierung der Elemente bereit. Index Wortstamm Datenbank Daten Daten Format Kapitel 2 1 2 1 Kapitel id 1 2 Inhalt <kapitel id=“1“>XML steht für <kursiv>Extensible…</kursiv> und … </kapitel> <kapitel id=“2“>…</kapitel> Abbildung 3.1: parallele Volltext-Indizierung und Volltext-Speicherung unter Verwendung von XMLType Anfragen an dokumentzentrierte XML–Daten beziehen sich häufig auf den Informationsgehalt von Nachrichten bzw. Texten, so wie das angesprochene Beispiel der Anfrage an Kapitel, die das Thema "Datenbank" behandeln. Im Bereich des Information Retrieval wurden Verfahren und spezielle Indizierungen von Volltexten entwickelt, um Anfrage auf Volltexten umzusetzen [Kur04]. Häufig werden bei dieser Art der Speicherung verschiedene Verfahren der Indizierung parallel eingesetzt, wie Wortstammindizierungen (das Wort "Datenbank" kommt in dem Dokument alleine nicht vor), exakte Indizierung, phonetische Indizierung etc. Zusätzlich wird häufig (insbesondere für semistrukturierte Dokumente) eine zusätzliche Strukturindizierung vorgenommen, um strukturierte bzw. strukturierbare Anteile von dokumentzentrierten und / oder semistrukturierten XML– Dokumenten, wie Eingrenzung auf bestimmte Kapitel, effizienter anfragen zu können. Durch die Speicherung der Zeichenkettenrepräsentation von Elementen, die ihrerseits auch weitere Elemente beinhalten können, wird die Wiederherstellbarkeit gewährleistet. Solche Methoden werden häufig in Suchmaschinen oder anderen Dokumentendatenbanken (Verlags- oder Bibliotheksdatenbanken) eingesetzt. Ihre nähere Erläuterung würde an dieser Stelle jedoch zu weit und nicht zum eigentlichen Ziel führen. Erschwerend käme hinzu, dass sich diese Arbeit insbesondere mit schemalosen XML–Daten beschäftigt, diese Methoden aber über die Grundstruktur der Elemente informiert sein müssen, um sie anfragen zu können. 18 3 XML in relationalen Datenbanken 3.2 Abbildung der Dokumentstruktur auf relationale Datenbankstrukturen Die im Folgenden vorgestellte Methode basiert auf der Idee, die Dokumentenstruktur eines XML–Dokuments abzubilden. Dokumentenstrukturen werden durch die Document Type Definition oder ein XML–Schema beschrieben. Diese Arbeit befasst sich insbesondere mit schemalosen Daten. Unter dieser Voraussetzung des Fehlens expliziter Strukturbeschreibungen lässt sich dieses Verfahren dennoch mit einem gewissen Aufwand zur Speicherung von schemalosen XML–Daten einsetzen. Insbesondere dann, wenn XML–Daten datenzentriert sind, sind ihre Strukturen in der Regel sehr genau durch Regeln beschreibbar. Häufig lassen sich in diesen Daten Sequenzen von immer gleichartigen Elementen finden, wie in dem Beispiel der Telefonliste die Elemente <eintrag> oder <nummer>, insbesondere dann, wenn die Daten aus Datenbanken oder vergleichbaren Speicherstrukturen heraus erzeugt wurden, die verschiedene Dateneinheiten vereinheitlicht als homogene Einträge von Tabellen oder Listen ablegen können. Im Gegensatz zu dokumentzentrierten Datenbeständen kann aus einem solchen Bestand auf Grundlage der Daten der Versuch unternommen werden, Strukturbeschreibungen abzuleiten und zu rekonstruieren. <?xml version="1.0"?> <db_xyz> <telefonliste> <name>Mustermann</name> <nummer>1234567</nummer> </telefonliste> <telefonliste> <name>Musterfrau</name> <nummer>2486</nummer> </telefonliste> </db_xyz> Aus der Betrachtung der gelieferten Daten können nun Rückschlüsse auf die zugrundeliegende Struktur gezogen werden. Unterhalb des Wurzelelements befinden sich ausschließlich <telefonliste> - Elemente (eine etwas unschöne Namensgebung), die ihrerseits zwei weitere Elemente enthalten. In einer DTD könnte diese Struktur durch <!ELEMENT telefonliste (eintrag)*> dargestellt werden. Statt dem Quantor * könnten auch spezifischere Quantoren wie + oder eine Festlegung auf genau zwei Elemente abgeleitet werden. Dies geschieht zum Beispiel bei den Kindelementen, die ihrerseits keine weiteren Elemente mehr beinhalten, die durch <!ELEMENT name <!ELEMENT nummer (PCDATA)> (PCDATA)> 19 3 XML in relationalen Datenbanken dargestellt werden. Elemente, die weitere Elemente beinhalten, werden bei einer Übertragung auf relationale Datenbankstrukturen als Relationen abgebildet, Elemente, die reinen Textinhalt beinhalten, als Attribute der jeweiligen Relation. So werden die <telefonliste> - Elemente als eine Relation telefonliste abgebildet. Die Kindelemente dieser Elemente werden als Attribute dieser Relation abgebildet. In den <name> - Elementen befinden sich Zeichenketten, in den <nummer> - Elementen befinden sich Ganzzahlen, dementsprechend werden die Datentypen der Attribute angepasst, so dass sich die folgende Datenbankstruktur ergeben könnte: telefonliste name:varchar2(20) nummer:INTEGER(14) Mustermann 1234567 Musterfrau 2486 Zwei Vertreter von Applikationen, die aus einem Beispielsatz von XML-Dokumenten die zugrundeliegende Datenstruktur in Form einer DTD automatisch ableiten und darstellen können, sind XTRACT ([GGR+ ]) und DTD-Miner ([MLN]). Der Vorteil dieses Verfahrens ist, dass XML–Daten in sehr klaren und relativ einfachen Strukturen abgelegt werden. Der Aufbau eines XML–Elements kann direkt aus der Datenbankstruktur abgelesen werden und komplexe Elemente können direkt angefragt werden. Ein XPath-Ausdruck wie //child::eintrag, der eine Repräsentation aller <eintrag> - Elemente der Beispieltelefonliste zurückliefert, kann direkt durch die SQL-Anfrage SELECT * FROM name repräsentiert werden, ohne dabei einen Umweg über die Navigation durch das Wurzelelement <telefonliste> nötig werden zu lassen. Ein weiterer Vorteil ist die generische Struktur der Datenbank, die regelbasiert aus der Strukturbeschreibung abgeleitet werden kann. Damit eignet sich dieses Verfahren in besonderem Maße für die computergestützte Erzeugung von Datenbankstrukturen und automatisierte Speicherung von XML–Daten. Eine Wiederherstellung des ursprünglichen Dokuments ist hier nur mit der Präzision möglich, mit der die ursprüngliche Dokumentstruktur abgebildet wurde. Zugeständnisse an die Effizienz der generierten Datenbankstrukturen (Vernachlässigung von Kommentarelemente, Verarbeitungsanweisungen etc. oder Vernachlässigung der Dokumentenordnung) können unter Umständen eine genaue Replikation des originalen Dokuments teilweise oder ganz unmöglich machen. Diese Form der Speicherung von (aus Beispielen abgeleiteten) Dokumentstrukturen hat allerdings vor allem bei schemalosen Daten, deren Struktur sich ändern kann, den Nachteil, dass häufig nicht bestimmt werden kann, wann eine ausreichende Testabdeckung gegeben ist, um eine verlässliche Strukturbeschreibung ableiten zu können. Ein neuer Datensatz könnte <nummer>278 oder 478</nummer> enthalten. Die Festlegung auf einen Integerdatentyp ist hierfür nicht geeignet, so dass der bislang verwendete Datentyp in einen neuen (unter Umständen sehr aufwändig) konvertiert werden muss. Elemente, die eventuell neu auftreten, müssen nachträglich als Attribute der Relationen 20 3 XML in relationalen Datenbanken eingefügt werden. Sind diese Elemente zusätzlich komplexe Elemente, die weitere Elemente beinhalten, müssen zusätzlich neue Relationen angelegt werden. 3.3 Abbildung des Document Object Model auf relationale Datenbankstrukturen Neben Methoden, die mehr oder weniger generisch aus der Dokumentstruktur eines XML–Dokumente oder vielmehr aus ihrer Beschreibung versuchen, Datenbankstrukturen abzuleiten, die geeignet sind, die Inhalte der XML–Dokumente zu speichern (ein Vertreter dieses Ansatzes wurden in dem vorherigen Abschnitt beschrieben) sowie speziellen Methoden zur Speicherung von Markup Data (aus dokumentzentrierten XML– Dokumente) versucht eine dritte Gruppe, eine Repräsentation der XML–Elemente in Form von DOM–Objekten zu betrachten und zu speichern. DOM betrachtet XML– Elemente als Knoten eines DOM–Baums, der das XML–Dokument objektorientiert modelliert und basiert dabei auf der Betrachtung eines XML–Elements als eine standardisierte Informationseinheit (standardisiert als sog. Information Set durch das W3C). In der Beschreibung des Document Object Model in Abschnitt 2.3 wird auf diese Betrachtung näher eingegangen. Methoden, die auf dieser Betrachtung von XML–Dokumenten und -Elementen basieren, haben gegenüber den in den vorherigen Abschnitten beschriebenen Methoden den Vorteil, zum einen einen hohen Automatisierungsgrad bei der Gestaltung der Datenbankstruktur zu ermöglichen. Zum anderen sind die Informationseinheiten und ihre Schnittstellen fest definierte API’s, in denen sich zwar die Struktur verschiedener Klassen von Objekten eines DOM–Baums (Element–Knoten, Kommentar–Knoten, Dokument– Knoten etc.) unterscheiden, nicht aber die Struktur von Objekten einer gemeinsamen Klasse. So verfügt ein Element–Knoten, unabhängig von seinem Inhalt, immer über die folgenden Schnittstellen: [namespace name] [local name] [prefix] [children] [attributes] [namespace attributes] [in-scope namespaces] [base URI] [parent] der Name des Elements inkl. Namensraum der Name ohne die Angabe des Namensraums die Bezeichnung des Namensraums eine geordnete Liste der Kinderknoten eine ungeordnete Menge der Attribute des Elements eine ungeordnete Menge der Attribute der Namensraumdeklaration eine ungeordnete Menge der Namensraumdeklarationen der Basis-URI des Elements der Elternknoten des Elements Jedes Objekt des Document Object Model stellt eine solche standardisierte Informationseinheit dar. Im Einzelnen sind die Definitionen der Informationseinheiten in [CT04] vollständig beschrieben und einsehbar. Das Beispiel soll lediglich anhand eines XML–Elements als eine der wichtigsten Informationseinheiten einen Einblick in den 21 3 XML in relationalen Datenbanken Aufbau dieser Einheiten geben. Die DOM–API modelliert diese Informationseinheiten und stellt Methoden für den Zugriff auf einzelne Informationen bereit (getPrefix(), getAttributes(), getParent() usw.). Durch die feste Definition der Schnittstellen und Eigenschaften, über die DOM– Objekte verfügen, ist es möglich, im Gegensatz zu vorher beschriebenen Methoden, die insbesondere Inhalt oder Struktur von XML–Elementen betrachten und diese abzubilden versuchen, Information Sets von XML–Elementen in festen Datenbankstrukturen abzubilden, die unabhängig von Inhalt und Struktur der eigentlichen Elemente sind. Beziehungen zwischen Elementen werden nicht aus Strukturbeschreibungen abgeleitet, sondern ergeben sich aus den Information Sets. Somit wird es möglich, ohne jede Kenntnis und ohne vorliegende Strukturbeschreibung ein generisches Verfahren zu entwickeln, welches es ermöglicht, eine Datenbankstruktur zu entwerfen, die geeignet ist, jedes beliebige XML–Dokument völlig unabhängig von Struktur und Inhalt zu speichern. Im folgenden Abschnitt sollen Methoden und Nummerierungsschemata für eine Abbildung von XML–Daten in relationalen Datenbanken entwickelt werden, die auf dem Document Object Model beruhen. Anforderungen an die entwickelte Methoden sollen neben der möglichst vollständigen Abbildung des XML–Dokuments die Schaffung ein Nummerierungsschema sein, das Anfragen an die gespeicherten Daten besonders hinsichtlich XPath- und hierauf basierenden XQuery–Anfragen möglichst effizient auswerten kann. 3.3.1 Entwicklung einer Datenbankstruktur auf Grundlage des Document Object Model An dieser Stelle sei kurz angemerkt, dass sich sämtliche Betrachtungen von DOM– Objekten im Folgenden auf die wesentlichen Eigenschaften der Objekte beschränken. Mit "wesentliche Eigenschaften" sind dabei diejenigen Eigenschaften gemeint, die häufig in XQuery-Anfragen von Interesse sind. Dabei handelt es sich im Wesentlichen um die Träger der eigentlichen Information eines XML–Elements (Text bei Textknoten, Attributname und -wert eines Attributknoten, Elementname etc.) sowie Informationen über die Position eines DOM–Objekts im zugehörigen DOM–Baum (parent - Verweis, Liste der Kinderknoten etc.), die zur Navigation in diesem DOM–Baum nötig sind. Die im Folgenden verwendeten Beispiele basieren im Wesentlichen auf dem XML–Beispiel 2.1. Um DOM–Objekte zu speichern, muss eine Datenbankstruktur in der Lage sein, seine Eigenschaften und Attribute zu speichern. Diese Eigenschaften sind die Inhalte des Information Set des entsprechenden XML–Elements. Neben einelementigen Eigenschaften wie [local name] und [prefix] gehören zu diesen auch (geordnete und ungeordnete) Elementmengen wie [attributes] und [children]. Verschiedenartige DOM–Objekte verfügen über unterschiedliche Eigenschaften. Der schlichteste Ansatz für eine generelle Datenbankstruktur, die unterschiedliche DOM– Objekte und ihre Eigenschaften vollständig abbildet, ist in Abbildung 3.3.1 angedeutet. Auf die Relationen zur Speicherung von DOM–Objekten, die in den hier verwendeten Beispielen ohnehin nicht auftreten (Kommentarknoten, Verarbeitungsanweisungen), 22 3 XML in relationalen Datenbanken document id element id attribute id nodeid child nodeid name ... text local_name child value parent id value attribute parent parent ... ... ... Abbildung 3.2: Einfache Datenbankstruktur zur Abbildung von DOM–Objekten wurde hier verzichtet. Primärschlüssel der Relationen sind die unterstrichenen Attribute id und ggf. nodeid als Teil eines zusammengesetzten Primärschlüssels. Der zweite Primärschlüssel nodeid in den Relationen element und document werden in diesem ersten Ansatz benötigt, um Objekte mit nicht atomaren Attributen wie child und attribute auf mehrere Tupel aufteilen zu können. Das Attribut parent der verschiedenen Relationen verweist auf Primärschlüssel bzw. Teile von Primärschlüsseln in einer der Relationen der Datenbankstruktur und stellt damit eine Fremdschlüsselbeziehung auf id - Attribute verschiedener Relationen dar. Die XML-Telefonliste besteht aus den Elementen <telefonliste>, <eintrag>, <name>, <vorname>, <vorwahl> und <nummer>. Eine Ablage der entsprechenden DOM–Objekte in der Relation führt zu einem Ergebnis, wie es in 3.3 angedeutet ist2 . Es ist hier zu beachten, dass die gefüllte Relation lediglich einen Teil der Sicht eines DOM–Objekts auf ein XML–Dokument abbildet. Die Textknoten, die aus DOM–Sicht zwischen den eigentlichen (Kind-) Elementknoten in der Liste der Kinderknoten stünden und Formatierungen für die Darstellung eines XML–Dokuments wie Zeilenumbrüche und Einrückungen beinhalten, wurden hier und werden im Folgenden bis auf weiteres aus Gründen des besseren Verständnisses ausgelassen. Die Relation liegt in der ersten Normalform vor. Hierzu wurden mehrdimensionale Merkmalsausprägungen wie die Liste der Kinderknoten dadurch als atomare Attribute abgebildet, dass DOM–Objekte auf mehrere Tupel abgebildet wurden. O.B.d.A. ist diese Art der Abbildung von DOM–Objekten bei weitem nicht die effektivste Möglichkeit. Unter anderem werden DOM–Objekte redundant auf mehrere Tupel verteilt, sofern sie über nichtatomare mehrelementige Eigenschaften wie Attribute und Kinderknoten verfügen. Ein erster Ansatz, um die Datenbankstruktur effizienter zu gestalten ist eine Einschränkung insbesondere der nichtatomaren abgebildeten Eigenschaften der DOM– Objekte. Es bietet sich an, die Liste der Kinderknoten aus den abgebildeten Eigenschaften herauszunehmen, denn über das parent - Attribut ist diese Eigenschaft rekursiv wiederherstellbar und damit redundant. Eine weitere redundante Eigenschaft von DOM– 2 Die Relation document ist hier aus Platzgründen nur ansatzweise gefüllt 23 3 XML in relationalen Datenbanken document element attribute id nodeid child doc1 1 doc1 doc1 doc1 doc1 ... text id value parent elem1 text1 Mustermann elem3 ... ... text2 Max elem4 9 elem9 text3 0511 elem5 10 text1 text4 1234567 elem6 ... ... text5 doc1 15 text6 text6 doc1 16 attr1 doc1 17 attr2 elem8 1234567 id nodeid local_name child elem1 1 telefonliste elem2 doc1 elem1 2 telefonliste elem3 doc1 elem2 3 eintrag elem3 attr1 elem1 elem2 4 eintrag elem4 attr1 elem1 elem2 5 eintrag elem5 attr1 elem1 elem2 6 eintrag elem6 attr1 elem1 elem3 7 name elem2 elem4 8 vorname elem2 elem5 9 vorwahl elem2 elem6 10 nummer elem2 elem7 11 eintrag elem8 attr2 elem1 elem7 12 eintrag elem9 attr2 elem1 elem8 13 vorwahl elem7 elem9 14 nummer elem7 id name value parent attr1 id 1 elem2 attr2 id 1 elem2 ... attribute parent elem9 ... ... Abbildung 3.3: Abgebildete DOM–Objekte in der Datenbankstruktur 24 3 XML in relationalen Datenbanken Objekten ist die Liste der Attributknoten, die ebenso wie die Liste der Kinderknoten rekursiv wiederherstellbar ist. Diese Einschränkungen in der Abbildung von DOM– Objekten führen dazu, dass keine mehrelementigen Eigenschaften mehr abgebildet werden müssen. Damit erübrigt sich die Notwendigkeit, zusammengesetzte Primärschlüssel zu verwenden und teilweise erhebliche Redundanzen werden vermieden. document id text ... doc1 attribute element value parent text1 Mustermann elem3 text2 Max elem4 text3 0511 elem5 text4 1234567 elem6 id name value parent attr1 id 1 elem2 text5 attr2 id 2 elem2 text6 id local_name parent elem1 telefonliste doc1 elem2 eintrag elem1 elem3 name elem2 elem4 vorname elem2 elem5 vorwahl elem2 elem6 nummer elem2 elem7 eintrag elem1 elem8 vorwahl elem7 elem9 nummer elem7 ... id ... elem8 1234567 elem9 ... Abbildung 3.4: Abgebildete DOM–Objekte unter Vernachlässigung der Eigenschaften child und attribute Eine solche Datenbankstruktur genügt bereits an dieser Stelle einer wichtigen Anforderung. Sie ist geeignet, jedes XML–Dokument abzuspeichern. Allerdings beinhaltet diese noch recht einfache Struktur einige Nachteile. Ein Nachteil ist, dass diese Struktur zwar DOM–Klassen in Form ihrer Information Sets modelliert, diese Klassen jedoch als voneinander unabhängige Typen ohne gemeinsame Eigenschaften betrachtet. Die DOM–API modelliert XML–Elemente als Knoten eines Baums, wobei ein DOM–Objekt eines DOM–Baums auch über typische Eigenschaften eines allgemeinen Knoten in der allgemeinen Datenstruktur "Baum" verfügt, die allen Knoten gemeinsam sind. Die DOM–API stellt für jede DOM–Klasse ein spezielles Interface bereit, die alle von einem gemeinsamen Superinterface Node abgeleitet sind, das die Merkmale eines Knoten modelliert. Das folgende UML–Diagramm der Ableitung der Interfaces der verschiedenen Knotentypen von einem gemeinsamen Interface Node soll lediglich grundsätzlich den Zusammenhang von spezifischen DOM–Typen mit spezifischen Methoden und Merkmalen und allgemeinen DOM–Knoten mit allgemeinen Merkmalen verdeutlichen. Die Methoden und Eigenschaften sind hier nicht vollständig notiert und die Ableitungshierarchie ist nicht vollständig. Es existieren mehr DOM–Typen als hier gezeigt. Außerdem ist das Interface Comment nicht direkt von Node abgeleitet, sondern von einem Subinterface von 25 3 XML in relationalen Datenbanken Node, das Knoten mit Textinhalten (Text, CDATASection und Comment) im Allgemeinen beschreibt. Diese gekürzte Variante soll dem allgemeinen Verständnis dienen. Diese Vererbungshierarchie kapselt allgemeine Eigenschaften eines Knoten von speziellen Eigenschaften eines speziellen Knotentyps. Diese Kapselung wird sich im weiteren Verlauf als nützlich erweisen und soll in eine Erweiterung der bisher entwickelten Datenbankstruktur einfließen. Mit dieser Erweiterung wird auch ein weiterer Nachteil des «interface» org.w3c.dom::Node +appendChild() +removeChild() +getChildNodes() +getParentNode() +getPreviousSibling() +getNextSibling() +getNodeValue() «interface» org.w3c.dom::Element +getTagName() +getAttribute(name) «interface» org.w3c.dom::Attr +getName() +getValue() +setValue(value) +getOwnerElement() +getSpecified() «interface» org.w3c.dom::Document +getDocumentType() «interface» org.w3c.dom::Comment +appendData(data) +deleteData() +getData() ... Abbildung 3.5: Interface–Hierarchie von DOM–Typen bislang bestehenden Datenbankentwurfs umgangen. DOM–Knoten können Kinderknoten anderer DOM–Knoten sein. In der Regel sind diese Hierarchien auf bestimmte Kombinationen beschränkt. So ist ein Textknoten immer ein Kinderknoten eines Elementknoten. Elementknoten hingegen können Kinderknoten anderer Elementknoten, aber auch des Dokumentknoten sein. Elementknoten erfordern also bei einem Verweis auf ihren Elternknoten die Möglichkeit, Fremdschlüssel auf zwei mögliche Relationen (die Elementrelation und die Dokumentrelation) zu besitzen, oder alternativ nur Bezeichner, die keine "echten" Fremdschlüssel darstellen, wodurch die Integrität der Daten gefährdet werden kann. Dieser Generalisierung entspricht die Datenbankstruktur, wie sie in Abbildung 3.6 dargestellt ist. Die id – Attribute stellen wiederum die Primärschlüssel der einzelnen Tupel der Relation node dar. Die id – Attribute der Relationen text, element und document dienen jeweils als Primärschlüssel sowie als Fremdschlüssel auf das id – Attribut der Relation node. Das id – Attribut der Relation attribute dient als Primärschlüssel dieser Relation, das hier neu eingeführte owner – Attribut der Relation attribute ist ein Fremdschlüssel, der das id – Attribut der Relation node referenziert. Die bislang entwickelte und in Abbildung 3.6 dargestellte Datenbankstruktur ist bereits geeignet, beliebige DOM–Objekte zu speichern. Anfragen an diese gespeicherten DOM–Objekte beziehen jedoch häufig bestimmte Eigenschaften verschiedener Objekte ein. Location Paths arbeiten im Wesentlichen mit den Elementnamen (local_name) 26 3 XML in relationalen Datenbanken node id 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 document parent_node 1 2 2 3 5 3 7 3 9 3 11 4 13 4 15 node_id 1 node_type document element element element element text element text element text element text element text element text text element document_type ... node id 6 8 10 12 14 16 value Mustermann Max 0511 1234567 node_id 2 3 4 5 7 9 11 13 15 tag_name telefonliste eintrag eintrag name vorname vorwahl nummer vorwahl nummer attribute id 1 2 1234567 name id id value 1 2 owner node 3 4 Abbildung 3.6: relationale Abbildung der Interface–Hierarchie von DOM–Typen von element – Knoten. Prädikate verarbeiten hauptsächlich Inhalte Inhalte (value) von text – Knoten. Um solche Anfragen auf dieser Datenbankstruktur umsetzen zu können, werden immer wieder aufwändige Joins der einzelne Relationen nötig sein. Um diese Joins zu umgehen und damit Vorteile in den Laufzeiten von Anfrageauswertungen zu erzielen, können die einzelnen Relationen, die Knoten des DOM–Baums speichern, bereits von vornherein zusammengefasst werden, so wie in Abbildung 3.7 dargestellt. Die entstehenden Redundanzen können in Kauf genommen werden, da hierdurch häufige Joins der Relationen vermieden werden können. Obgleich die bislang entwickelte Datenbankstruktur bereits in der Lage, beliebige DOM–Objekte und -Hierarchien abzubilden, enthält sie immer noch strukturelle Unzulänglichkeiten hinsichtlich Wiederherstellbarkeit und Navigation auf XPath–Achsen. Eine Unzulänglichkeit ist die einseitige funktionale Beziehung zwischen Eltern- und Kinderknoten, in der lediglich die Kinderknoten ihren Elternknoten kennen, die Elternknoten hingegen zur Vermeidung von Redundanzen nicht mehr ihre Kinderknoten direkt kennen. Dadurch sind diese Beziehungen nur rekursiv wiederherstellbar. XPath–Achsen können jedoch vorwärts- sowie rückwärtsgerichtet sein. Es ist also leicht einsehbar, dass für eine effiziente Auswertung von XPath–Navigationen diese Beziehungen nicht nur rückwärts (Kinderknoten kennen ihren Elternknoten), sondern insbesondere auch vorwärts (Elternknoten kennen ihre Kinderknoten) schnell herstellbar sind. Darüber hinaus sollten nicht nur diese direkten Beziehungen zwischen zwei direkt verbundenen Knoten schnell herstellbar sein, sondern auch besonders in Hinblick auf die XPath–Achsen ancestor und descendant, die die indirekten Vorgänger bzw. Nachfolgerknoten aus der Sicht des DOM–Baums bezeichnen sowie der XPath–Achsen preceding und following, die die Vorgänger bzw. Nachfolgerelemente bezüglich der Dokumentenordnung bezeichnen. 27 3 XML in relationalen Datenbanken node id parent node_type 1 attribute local_name value document 2 1 element telefonliste 3 2 element eintrag 4 2 element eintrag 5 3 element name 6 5 text 7 3 element 8 7 text 9 3 element 10 9 text 11 3 element 12 11 text Mustermann Max 4 element 13 text 15 4 element 16 15 text name value 1 3 id 1 2 4 id 2 nummer 1234567 13 owner vorwahl 0511 14 id vorname vorwahl nummer 1234567 ... Abbildung 3.7: Zusammenlegung der einzelnen Relationen 28 ... 3 XML in relationalen Datenbanken Eine weitere Unzulänglichkeit wurde bereits indirekt im vorhergehenden Absatz erwähnt und betrifft die Erhaltung der Dokumentenordnung, die wichtig für die Wiederherstellbarkeit des XML–Dokuments oder von XML–Fragmenten ist und deren Verlust wie bereits in der Einleitung zu diesem Kapitel erwähnt durchaus zu einem Verlust an Information führen kann. In den bislang gezeigten Beispielen wurde die Dokumentenordnung immer implizit beachtet und ist ansatzweise in der Vergabe der id – Attribute, die eigentlich der Primärschlüsselvergabe dienen sollen, abgebildet. Eine andere Wahl von Primärschlüsseln als inkrementierte Zahlenwerte oder eine andere Reihenfolge bei der Betrachtung und Speicherung von XML–Elementen würde diese implizite Ordnung zerstören. Die Erhaltung der ursprünglichen Dokumentenordnung sowie die Anforderung an effiziente Auswertungsmöglichkeiten für alle XPath–Achsen lassen es notwendig erscheinen, die bereits entwickelte Datenbankstruktur um Informationen zu erweiterten, so dass sie dieser Anforderung gerecht wird. Im nächsten Abschnitt sollen einige Vorgehensweisen für Nummerierung und Indizierung von Knoten vorgestellt werden, die dieser Anforderung gerecht werden. 3.3.2 Entwicklung eines Nummerierungsschemas aus dem Nested Set Model Die vorgestellte entwickelte Datenbankstruktur ist geeignet, ein beliebiges XML– Dokument zu speichern. Über die Vergabe des id – Attributs als Primärschlüssel wurde bereits die Grundlage für eine Nummerierungsschema der Knoten gelegt. Insbesondere, wenn diese Abbildung abstrakte Datenstrukturen abbildet, kann eine implizite Ordnung (durch die Reihenfolge, in der abgebildete Knoten gespeichert wurden o.ä.) unter Umständen nicht mehr erhalten bleiben. Ein Schema für die Nummerierung von Knoten einer Baumstruktur kann dafür eingesetzt werden, die Ordnung des Baums bei einer Abbildung auf relationale Strukturen zu erhalten. Wird in dem bislang entwickelten Datenbankmodell das id – Attribut vorerst ausser acht gelassen (eine explizite Nummerierung war hierfür nicht vorgesehen), kann der erste Ansatz für ein Nummerierungsverfahren eine Abbildung der Baumstruktur sein. Ohne eine strukturierte Nummerierung der Knotenreihenfolge in der Baumstruktur kann eine Wiederherstellung der Baumstruktur einerseits lediglich rekursiv erfolgen (Knoten kennen in dieser Struktur ihren Elternknoten, aber nicht ihre Kinderknoten), was unter Umständen speicher- oder laufzeitintensiv werden kann, andererseits sind auch keine Informationen über Nachbarschaftsbeziehungen wie next-sibling vorhanden, so dass zwar die hierarchische Ordnung, aber nicht die Dokumentenordung erhalten bleibt. Die "Wiederherstellung" der Baumstruktur ist neben der Wiederherstellung des originalen Dokuments auch für die Auswertung von XPath–Anfragen, die für diese Struktur optimiert sind, erforderlich. Verschiedene Nummerierungschemata, häufig abgeleitet aus Durchlaufordnungen von Bäumen, werden je nach Anforderung in der Informatik eingesetzt. Drei der bekanntesten Durchlaufordnungen sind die Präordung (preorder), Postordnung (postorder) und die 29 3 XML in relationalen Datenbanken Stufenordnung (level order), die als bekannt vorausgesetzt werden können. Ein Durchlauf in Präordnung kann auch durch einen Eulerschen Durchlauf erreicht werden. Diese Durchlaufordnung kann anschaulich als eine zyklenfreie Wanderung um den Baum herum ohne dabei Kanten zwischen Knoten zu schneiden, beschrieben werden, bei der jeder Knoten des Baums einmal "von links" und einmal "von rechts" besucht wird. 1 2 7 3 4 5 8 9 6 Abbildung 3.8: Eulerscher Durchlauf durch einen Baum Der folgende Algorithmus beschreibt diesen Umlauf ausgehend vom Wurzelknoten eines (Teil-) Baums rekursiv. initResult() bezeichnet die Initialisierung einer Laufvariablen, sofern sie noch nicht initialisiert wurden bzw. die Inkrementierung dieser Variable result bezeichnet diese Laufvariable Tree bezeichnet die Datenstruktur "Baum", deren Knoten vom Datentyp Node sind; Tree verfügt über eine Methode childList(Node) childList(Node p) liefert die geordnete Liste der Nachfolgerknoten von Node p Node bezeichnet eine Datenstruktur, die einen Knoten in einem Tree modelliert; Node verfügt über die Methoden markLeft(Node, result), markRight(Node, result) sowie isLeaf(Node, result) markLeft(Node p, result) weist der linken Koordinate eines Knoten p den Wert result zu markRight(Node p, result) weist der rechten Koordinate eines Knoten p den Wert result zu 30 3 XML in relationalen Datenbanken isLeaf(Node p) liefert den Wahrheitswert wahr, wenn der Knoten p über keine weiteren Nachfolgerknoten mehr verfügt, andernfalls liefert diese Methode den Wahrheitswert falsch algorithm eulerTour(Tree t, Node p) result ← initResult(); if isLeaf(p) then markLeft(p, result); markRight(p, result); else markLeft(p, result); for each c in t.childList(p) do eulerTour(Tree c, Node p); markRight(p, result); return result; Eine sinnvolle Nummerierung ist die fortlaufende Nummerierung in der Reihenfolge, in der die Knoten in diesem Durchlauf angetroffen werden. Bereits nummerierte Knoten behalten ihre Nummern. Diese Nummern werden dem id – Attribut des Datenbankentwurfs zugeordnet. Vorerst werden nur die linken Koordinaten besetzt, so dass jedem Knoten genau eine Koordinate zugewiesen wird. Die Methode markRight bleibt vorerst ohne Auswirkungen, diese Methode wird an späterer Stelle interessant. Diese Art der Nummerierung wurde bereits in Abbildung 3.8 vorgenommen. Da benachbarte Knoten in der Reihenfolge besucht werden, wie sie in ihrem Baum angeordnet sind, wird die ursprüngliche Ordnung des Baums erhalten und ist wiederherstellbar. Damit ist auch die ursprüngliche Dokumentenordnung wiederherstellbar. So ist bereits eine wichtige Anforderung an dieses Nummerierungsschema erfüllt. Die effiziente Umsetzung von Navigationen auf XPath–Achsen ist durch diese einfache Indexstruktur und die bislang entwickelte Datenbankstruktur noch nicht gewährleistet, da durch sie bislang lediglich der Axis Step element::parent() (durch das parent – Attribut) sowie die Vereinigung der Achsen following und descendant sowie die Vereinigung der Achsen preceding und ancestor abgebildet werden. Der Durchlauf in Präordnung liest einen Baum "von oben nach unten" und "von links nach rechts". In genau dieser Reihenfolge wird auch das zugrundeliegende XML–Dokument gelesen, wenn es vorwärts gelesen wird. Die Vereinigung der Achsen following und descendant bezeichnet alle Elemente, die nach einem aktuellen Element auftreten. Diese werden später besucht und erhalten so einen höheren Index, so dass alle Elemente, die mit einem höheren Index als das aktuelle Element versehen sind, entweder zu der Achse following oder descendant gehören. Entsprechende Überlegungen gelten für Elemente, die einen kleineren Index als ein aktuelles Element haben. Um eine Navigation entlang der vier wichtigsten Achsen effizient zu ermöglichen, muss eine Indexstruktur entwickelt werden, die einfach entscheiden kann, ob ein Knoten zu 31 3 XML in relationalen Datenbanken der Elementmenge gehört, die eine Achse bezeichnet. Als "einfache" Entscheidung soll hier eine Entscheidung bezeichnen, die in möglichst konstanter Laufzeit die Zugehörigkeit eines Elements zu einer bestimmten Elementmenge erkennen kann. Dies schließt aus, dass eine solche Entscheidung zum Beispiel durch eine Rekonstruktion von (Teil-) Bäumen getroffen wird, die nicht in konstanter Laufzeit geschehen kann. Die bislang entwickelte Indexstruktur kann diese Entscheidung bereits in konstanter Laufzeit hinsichtlich Achsen treffen, die die Anordnung von Elementen im Dokument betreffen, da die Elemente in dieser Reihenfolge indiziert werden. Eine Aussage über die Position eines Elements in einem XML–Dokument kann durch eine Vergleichsoperation von Knotennummern getroffen werden. Entscheidungen hinsichtlich Achsen, die die Hierarchie von Elementen abbilden (ancestor, descendant), können hiermit nicht einfach entschieden werden. Ein Modell, dass Bäume hinsichtlich ihrer hierarchischen Ordnung und Abhängigkeiten betrachtet, ist das Nested Sets Model. Das Nested Sets Model betrachtet einen Baum als eine Menge in sich geschachtelter Knotenmengen. Die Schachtelung dieser Knoten bildet dabei die hierarchische Ordnung des Baums ab. Ein Nested Set enthält den kompletten Teilbaum eines Baums ausgehend von einem bestimmten Knoten als Wurzel des Teilbaums. 1 2 7 3 4 5 8 9 6 Abbildung 3.9: Nested Sets in einem Baum Abb. 3.7 soll das Nested Sets Model des Baums aus Abb. 3.6 verdeutlichen. Den Blattknoten des Baums sind ebenfalls Nested Sets zugeordnet, die aus Gründen der Übersicht in der Abbildung nicht angedeutet sind. Jedem Knoten kann genau ein Nested Set zugeordnet werden, das diesen Knoten als Wurzelknoten und einen davon ausgehenden (evtl. leeren) Teilbaum enthält. Die Abbildung der hierarchischen Ordnung eines Baums wird mit diesem Modell dadurch erreicht, dass ein Nested Set immer nur einen Teilbaum ausgehend von einem Wurzelelement enthält und damit lediglich Knoten, die immer in einer hierarchischen 32 3 XML in relationalen Datenbanken Beziehung stehen. Wie die Zugehörigkeit von Knoten zu den auf die Dokumentenordnung bezogenen Vereinigungen der Achsen following und descendant beziehungsweise preceding und ancestor in konstanter Laufzeit entschieden werden kann, wurde bereits anhand der Nummerierung der Knoten in Präordnung gezeigt. Die Zuordnung innerhalb der Gruppe following und descendant beispielsweise kann durch die Entscheidung, ob zwischen einem zu testenden Knoten und dem aktuellen Knoten eine hierarchische Beziehung besteht, getroffen werden, denn descendant beschreibt die Menge aller Kinderund Enkelknoten und following alle Knoten, die in Dokumentenordnung folgen und keine Kinder- oder Enkelknoten eines gegebenen Knoten sind. Analog gilt dies für die entsprechenden rückwärtsgerichteten Achsen. Das Nested Sets Model lässt sich ausnutzen, um das bislang entwickelte Nummerierungsschema so zu erweitern, dass diese Entscheidungen ebenfalls in konstanter Laufzeit getroffen werden können. Der Grundgedanke ist dabei, die geschachtelte Struktur des Nested Set Model möglichst geschickt auf ein Nummerierungsschema abzubilden. Die vorangegangene Nummerierung der Knotenreihenfolge hat den Knoten eines Baumes bereits ein eindimensionales Koordinatensystem zugeordnet. Nested Sets hingegen beschreiben nicht genau eine Position eines Knoten, sondern eine Menge von Knoten. Ist diese Menge von Knoten mit einer passenden Ordnung indiziert, lässt sich diese Menge durch eine obere und untere Grenze genau beschreiben. Eine passende Ordnung der Knoten muss dabei gewährleisten, dass Nachfahren von Knoten immer eine höhere Ordnungszahl erhalten als ihre Vorgänger und rechte Nachbarknoten eine höhere Ordnungszahl als ihre linken Nachbarknoten erhalten. Einem Knoten können so zwei Indizes zugeordnet werden, die die Grenzen, in denen die Ordnungszahlen der Knoten des hiervon ausgehenden Teilbaums liegen, beschreiben. Die Nummerierung der Knoten in Präordnung stellt genau diese Ordnung der Knoten her. Jeder Nachfahre wird nach seinem Vorgänger besucht, die Liste der Kinderknoten wird geordnet von links nach rechts durchlaufen. Für ein erstes Beispiel, wie die Informationen der Nested Sets aus Abb. nested abgebildet werden können, soll diese Nummerierung als Grundlage verwendet werden. Das Nested Set, das den Teilbaum enthält, der den mit "2" indizierten Knoten als Wurzelknoten enthält, kann durch die Grenzen 3 und 6 beschrieben werden. Alle Knoten, die sich in diesem Nested Set befinden, besitzen einen Index, der zwischen (einschließlich) diesen beiden Grenzen liegt. Entscheidungen zu Vorgänger–Nachfolgerbeziehungen zwischen Knoten lassen sich durch diese Form der Indizierung sehr schnell durch einen Vergleich ihres Hauptindex (wie die Nummerierung in Präordnung vorerst genannt werden soll) und den zugeordneten Grenzen, in denen sich Indizes von Knoten eines gemeinsamen Nested Set bewegen, getroffen werden. Diese Art der Indizierung legt bereits eine wichtige Grundlage für die Auswertung von XPath–Achsen, indem sie in Verbindung mit der vorangegangenen Nummerierung die Zugehörigkeit eines Knoten zu einer Knotenmenge, die durch die vier Haupt–XPath– Achsen beschrieben wird, durch einfache Vergleichsoperationen von Hauptindizes und Nested Set Nummerierungen in konstanter Laufzeit bestimmen kann. Es stellt sich allerdings die Frage, inwiefern eine mehrfache Indizierung überhaupt nötig ist. Um den Hauptindex (die Präordnung) zu erstellen, muss jeder Knoten eines Baums mindestens einmal besucht werden. Um zusätzlich die Nested Set Indizes zu erstellen, muss im An- 33 3 XML in relationalen Datenbanken schluss der komplette Baum nochmals durchlaufen werden, da diese Indizes auf dem Hauptindex aufbauen. Zusätzlich muss zu jedem Knoten der komplette Teilbaum, der von diesem Knoten als Wurzelknoten ausgeht, durchlaufen werden, um das Minimum bzw. das Maximum der enthaltenen Hauptindizes der Nachfahren zu bestimmen. Geschickte Implementierung können diese mehrfachen Indizierungen jedoch umgehen. Eine Abwandlung des bislang entwickelten Nummerierungsschema hingegen ändert die Indizierung so ab, dass eine komplette Indizierung mit einem einzigen Durchlauf durch den Baum ermöglicht wird. 1,18 2,11 3,4 12,17 5,10 6,7 13,14 15,16 8,9 Abbildung 3.10: Indizierung des Nested Set Model Die Idee ist dabei, den vorgestellten Eulerschen Umlauf um den Baum, der bereits für die Nummerierung in Präordnung eingesetzt wurde, für diese Zwecke zu benutzen. In einem Eulerschen Umlauf wird jeder Knoten erst von links besucht, dann die Liste seiner Kinderknoten und abschließend wird der Knoten von rechts besucht. Dieser Umlauf, ausgehend von einem Wurzelknoten eines (Teil-) Baums, umläuft alle Kinderknoten der Wurzel, bevor er wieder an den Wurzelknoten zurückgelangt. Bei einer aufsteigenden Nummerierung der vergebenen Koordinaten lassen einige Eigenschaften dieser Indexstruktur Entscheidungen über XPath–Achsen effizient entscheidbar werden. Unter der Voraussetzung der Vergabe stetig ansteigender Koordinaten gilt offensichtlich, dass die linke Koordinate eines Knoten kleiner als die rechte Koordinate ist, denn die Umlaufrichtung bestimmt, dass der Knoten erst von links und dann von rechts besucht wird. Hat dieser Knoten Nachfahren, so bestimmt die Umlaufrichtung, dass der Knoten von links, dann alle Kinderknoten und abschließend der Knoten von rechts besucht wird. Für die Nachfahren gilt dasselbe und zusätzlich, dass ein ansteigender Index vergeben wird. Die Menge an Koordinaten, die in dem Intervall enthalten sind, das die linke und die rechte Koordinate aufspannen, wächst mit jedem Kinderknoten. Die Koordinaten der Nachfahren sind vollständig in dem Intervall enthalten, das die Koordinaten aufspannen. 34 3 XML in relationalen Datenbanken Die Zugehörigkeit eines Knoten zu einer Knotenmenge, die die XPath–Achse descendant auswählt, ist demnach in konstanter Laufzeit durch zwei Vergleichsoperationen zu bestimmen. Da diese Zugehörigkeit in konstanter Laufzeit bestimmbar ist und diese Achse mit vertauschten Rollen von Knoten im Prinzip dasselbe beschreibt wie die Achse ancestor, ist diese Zugehörigkeit durch eine entsprechende Umformulierung der Vergleichsoperationen ebenfalls in konstanter Laufzeit bestimmbar. l1,r1 l1,r1 l2,r2 ... ln,rn l1 < l2 < ... < rn < r1 l1 < r1 Abbildung 3.11: Ableitung der descendant – Achse aus den Nested Set Koordinaten, lx und rx bezeichnen die linke bzw. rechte Koordinate Die XPath–Achse following wählt alle Knoten aus, die in der Ordnung des Baums auf einen Knoten folgen. Durch aufsteigende Nummerierung der Knoten während eines Umlaufs erhalten diese Knoten höhere Koordinaten. Es ist also leicht einzusehen, dass für einen Knoten, der in Dokumentenordnung folgend ist, gilt, dass seine Koordinaten größer sind. Es genügt bei diesem Vergleich die Betrachtung der linken Koordinate, da die rechte Koordinate ohnehin größer als die linke ist. Die Achse preceding beschreibt dieselbe Knotenbeziehung mit vertauschten Knotenrollen. Durch eine entsprechende Umformulierung der Vergleichsoperation kann die Zugehörigkeit eines Knoten zu dieser Knotenmenge ebenfalls in konstanter Laufzeit entschieden werden. ancestor - descendant preceding - following ... l1,r1 l1,r1 l2,r2 l2,r2 [l2, r2] [l1, r1] = l1 < r1 < l2 < r2 [l2, r2] [l1, r1] l1 < l2 < r2 < r1 Abbildung 3.12: Hierarchische Ordnung und Dokumentordnung anhand von Nested Set Koordinaten, lx und rx bezeichnen die linke bzw. rechte Koordinate 35 3 XML in relationalen Datenbanken Durch dieses Indizierungsschema sind Knotenzugehörigkeiten zu den vier wichtigsten Achsen descendant, ancestor, preceding und following in konstanter Laufzeit berechenbar. Zu jeder dieser vier Achsen existieren in XPath je eine Spezialisierung. Die Spezialisierung der Achse descendant ist die Achse child, die nicht alle Nachfahren, sondern nur die direkten Nachfolgerknoten beinhaltet. parent ist eine Spezialisierung der ancestor – Achse, die nicht alle Vorfahren, sondern nur den Elternknoten enthält. preceding-sibling und following-sibling sind Spezialisierungen der Achsen preceding und following, die in Dokumentenordnung folgende bzw. vorhergende Nachbarknoten, also Knoten mit demselben Elternknoten beschreiben. Die bislang entwickelte Datenbankstruktur sieht vor, dass ein gespeicherter DOM– Knoten einen Verweis auf seinen Elternknoten als ein Attribut erhält. Die Achse parent ist damit bereits direkt in dem Datenbankentwurf abgebildet und kann in konstanter Laufzeit abgefragt werden. Knoten der Achse child sind Nachfahren eines Knoten (gehören also zu der Achse descendant) mit der Besonderheit, dass sie diesen Knoten als gemeinsamen Elternknoten besitzen. Zugehörigkeit zur descendant – Achse ist in konstanter Laufzeit bestimmbar, zur parent – Achse ebenfalls, so dass die Zugehörigkeit eines Knoten zur child – Achse ebenfalls in konstanter Laufzeit bestimmt werden kann. Benachbarte Knoten, wie sie durch die beiden sibling – Achsen beschrieben werden, verfügen über dieselbe Eigenschaft wie Knoten einer child – Achse. Sie verfügen über einen gemeinsamen Elternknoten. Zugehörigkeit zu den Achsen preceding bzw. following sind in konstanter Laufzeit bestimmbar, zur parent – Achse ebenso, also sind auch Zugehörigkeiten zu den Achsen preceding-sibling und following-sibling in konstanter Laufzeit bestimmbar. Die Vergabe von Koordinaten basierend auf dem Nested Set Model eines DOM–Baum, wie sie vorgestellt wurde, ist also im Allgemeinen geeignet, einen DOM–Baum zu indizieren, da Zugehörigkeiten von Knoten zu den wesentlichen XPath–Achsen durch diese Indizierung effizient in konstanter Laufzeit ausgewertet werden können. Eine kleine Erweiterung dieser Indizierung um die Forderung nach einer Inkrementierung des Laufindex mit einer konstanten Schrittweite (der Einfachheit halber von 1) lässt neben den bereits angesprochenen effizienten Auswertungen der XPath–Achsen noch einige zusätzliche Schlüsse zu. Offensichtlich lassen sich bei einer solchen Spezialisierung der Nummerierungsschrittweite weitere, spezielle Informationen des Information Set bzw. der DOM–API aus den Koordinaten ablesen oder herleiten. So unterscheiden sich die linke und rechte Koordinate eines Knoten genau dann um den Wert 1, wenn es sich bei dem Knoten um einen Blattknoten des DOM–Baums handelt. Ob ein Knoten weitere Kinderknoten enthält (hasChildren()), lässt sich anhand dieses Umstands einfach entscheiden, ohne über die Struktur des Baums informiert zu sein. Die linke Seite eines rechten Nachbarn eines Knoten wird durch die Umlaufreihenfolge unmittelbar nach der rechten Seite seines Vorgängers besucht und nummeriert. Es ist also leicht einsehbar, dass sich diese Koordinaten um den Wert 1 unterscheiden. Es kann also von einem Knoten direkt auf seinen rechten Nachbarn (nextSibling()) bzw. auf seinen Vorgänger (previousSibling()) geschlossen werden. 36 3 XML in relationalen Datenbanken ... l1,r1 l1,r1 l1,r1 l2,r2 r1 = l1 + 1 ... l2,r2 ln,rn l2 = l1 + 1 , r1 = rn + 1 l2 = r1 + 1 Abbildung 3.13: Koordinatenverhältnisse bei konstanter Schrittweite der Indizierung Nach der linken Seite eines Knoten wird die Liste der Kinderknoten besucht. Offensichtlich gilt, dass der erste Knoten der Liste der Kinderknoten eine um 1 inkrementierte linke Koordinate als seinen Elternknoten erhält (firstChild()). Entsprechende Überlegungen gelten für die rechten Koordinaten bezüglich dem letzten Knoten der Liste der Kinderknoten (lastChild()). Da jedem Knoten zwei Koordinaten zugeordnet werden und Koordinaten von Nachfahren immer zwischen den Koordinaten ihrer Vorfahren liegen und aufsteigend mit einer festen Schrittweite von 1 vergeben werden, ist ebenfalls leicht einzusehen, dass am Abstand der Koordinaten eines Elternknoten die Anzahl aller Nachfahren abgelesen werden kann. Ein Intervall [l, r], das ein Intervall natürlichen Zahlen begrenzt, enthält genau r − l + 1 verschiedene natürliche Zahlen. Zwei dieser Zahlen stellen die Intervallgrenzen und damit die Koordinaten des Elternknoten dar, es verbleiben also offensichtlich r−l−1 natürliche Zahlen in dem Intervall, von denen jede als eine Koordinate der Nachfahren verwendet wird. Jeder Nachfahre erhält zwei Koordinaten, also kann auf insgesamt r−l−1 2 Kinder- und Enkelknoten geschlossen werden. 3.3.3 Eine sequentielle Betrachtung des Nested Set Model Eine sequentielle Betrachtung für diese Art der Indizierung soll im Folgenden betrachtet werden. Im Unterschied zu einer Baum–Repräsentation vom XML–Daten werden hier XML–Dokumente direkt als Zeichenstrom betrachtet, der sequentiell eingelesen wird. Da die Reihenfolge, in der bei einem sequentiellen Einlesen auf DOM–Knoten bzw. XML–Elemente getroffen wird (Dokumentenordnung) dieselbe Reihenfolge ist, in der Knoten in Präordnung bzw. durch einen Eulerschen Umlauf indiziert werden, entspricht die sequentielle Ordnung der in Abb. 3.15 dargestellten topologischen Sortierung des DOM–Baums. Die hier entstehende Nummerierung entspricht, sofern sie ebenfalls mit einer Schrittweite von 1 durchgeführt wird, der Nummerierung, die durch den beschriebenen Eulerschen Umlauf entsteht. 37 3 XML in relationalen Datenbanken ... <elem_1> <elem_2> <elem_3>...</elem_3> </elem_2> <elem_4>...</elem_4> </elem_1> ... Wird dieses Fragment eines XML–Dokuments sequentiell eingelesen, entsteht eine Einlesereihenfolge, wie sie in Abb. 3.15 gezeigt wird. Die "Zeichen" – Achse beschreibt die Position des Lesekopfs in dem Zeichenstrom, den das XML–Fragment darstellt. ... <elem_1><elem_2><elem_3>...</elem_3></elem_2><elem_4>...</elem_4></elem_1> ... Zeichen c1 c2 c3 c4 c5 c6 c7 c8 Abbildung 3.14: Intervallschachtelung eines XML–Dokuments Die Werte c1 bis c8 bezeichnen in Abb. 3.15 Positionen auf dem Zeichenstrom des XML–Dokuments, werden im Folgenden jedoch äquivalent zu Indizes verwendet, für die die Voraussetzung ci < cj ⇔ i < j gilt. Die Reihenfolge, in der die XML–Elemente in dem Fragment angetroffen werden, ist dieselbe Reihenfolge, in dem der entsprechende XML–Baum in Präordung durchlaufen würde. In Präordnung wird rekursiv ein Knoten besucht und danach die geordnete Liste seiner Kinderknoten. Die Notation von XML–Elementen erfolgt durch ein Starttag, gefolgt von der Liste der child – Elemente und einem abschließenden passenden Endtag und entspricht damit der Ordnung, die durch die Präordnung hergestellt wird. In dieser Hinsicht bewährt sich die grundsätzliche Verwendung dieser Form eines Eulerschen Umlaufs bereits, denn so können Durchläufe durch Baumstrukturen auch durch das sequentielle Einlesen von XML–Elementen simuliert werden, ohne dabei tatsächlich über diese Strukturen zu verfügen. Ressourcenintensive Algorithmen wie der beschriebene Algorithmus für den Eulerschen Umlauf können damit an Systemgrenzen wie verfügbarer Arbeitsspeicher angepasst werden. Die Struktur eines durch Tags strukturierten XML–Dokuments entspricht der Struktur eines geklammerten Ausdrucks oder auch der Struktur einer Intervallschachtelung, wie in Abb. 3.15 angedeutet. Die Schachtelung ist streng hierarchisch (well–formed XML) geordnet. Ein Element ist immer vollständig in einem anderen enthalten (mit Ausnahme des Wurzelelements). Diese Voraussetzung lässt einige Schlüsse zu. Das Intervall [x1 , y1 ], das die Position im Zeichenstrom am Beginn eines Starttags (x1 ) sowie am Ende eines Endtags (y1 ) eingrenzt, ist vollständig in einem anderen Intervall [x2 , y2 ] enthalten, insbesondere gilt also x1 > x2 und y1 < y2 . Da jede Position auf dem 38 3 XML in relationalen Datenbanken Zeichenstrom als ein eindeutiger und aufsteigender ganzzahliger Index verwendet wird, konvergieren die Intervallgrenzen mit tiefer werdender Schachtelung. Für ein Intervall [x0 , y0 ], dass in dem Intervall [x1 , y1 ] geschachtelt vorliegt, gilt dementsprechend x0 > x1 und y0 < y1 . Daraus folgt x0 > x2 und y0 < y2 . Eine Verwendung dieser Intervallgrenzen ist neben dem Eulerschen Umlauf zur Indizierung der hierarchischen Beziehungen zwischen Knoten geeignet. Diese Grenzen werden im folgenden als die Koordinaten lef t und right eines Knoten bezeichnet. Beide Koordinaten sind eindeutig, für jedes Knotenpaar K und J mit K 6= J gilt lef t(K) < right(K) sowie lef t(K) 6= lef t(J), right(K) 6= right(J). Beide Koordinaten sind damit als Primärschlüssel geeignet. Durch die mit zunehmender Tiefe konvergierenden Grenzen ist die Entscheidung der Zugehörigkeit eines Knoten zu der XPath–Achse descendant ausgehend von einem Knoten K in konstanter Laufzeit zu beantworten. Für einen Knoten J dieser Knotenmenge gilt, dass seine Koordinaten Jlef t und Jright ein Intervall beschreiben, das vollständig im Intervall [Klef t , Kright ] liegt. Es gilt Jlef t > Klef t , Jright < Kright . Ob ein Knoten auf der entsprechenden rückwärtsgerichteten Achse liegt, kann analog entschieden werden, denn diese Entscheidung basiert auf der offensichtlichen Tatsache, dass für einen Knoten J, der zu der Menge der Vorfahren eines bestimmten Knoten K gehört, im Umkehrschluss gilt, dass der Knoten K zu den Nachfahren von J gehört. Es gilt also bei vertauschten Knoten für die XPath–Achse ancestor analog Klef t > Jlef t , Kright < Jright . Wie bereits im vorangegangenen Abschnitt gezeigt wurde, spezialisieren die sibling – Achsen die preceding – bzw. following – Achsen auf Knoten, die dieselben Elternknoten besitzen. Der Elternknoten wird nach dem bislang entwickelten Datenbankschema als Attribut eines Knoten verwendet und ist damit in konstanter Laufzeit verfügbar. Demnach sind Zugehörigkeiten zu sibling – Achsen ebenfalls in konstanter Laufzeit bestimmbar. Zugehörigkeit eines Knoten zur parent – Achse ist durch das parent – Attribut direkt abgebildet und in konstanter Laufzeit berechenbar. Die child – Achse spezialisiert die bereits beschriebene ancestor – Achse auf Knoten, die über denselben Elternknoten verfügen und ist nach analogen Überlegungen zu den sibling – Achsen ebenfalls in konstanter Laufzeit berechenbar. 3.3.4 Zusammenfassung der Datenbankstruktur In Abbildung 3.15 wird die abschließende Version der entwickelten Datenbankstruktur dargestellt. Die eingefügten Tupel entstammen der DOM–Repräsentation des in Beispiel 2.1 genannten XML–Dokuments, deren Knoten nach dem vorgestellten Indizierungsschema nummeriert wurden. Die Attribute lft und id bezeichnen die Primärschlüssel der entsprechenden Relationen. Das Attribut owner der Relation attribute ist ein Fremdschlüssel auf den Primärschlüssel der Relation node. 39 3 XML in relationalen Datenbanken node lft rgt parent node_type 1 32 2 31 1 element telefonliste 1 3 20 2 element eintrag 1 4 7 3 element name 5 6 4 text 8 11 3 element 9 10 8 text 12 15 3 element 13 14 12 text 16 19 3 element 17 18 16 text 21 30 2 element eintrag 1 22 25 21 element vorwahl 1 23 24 22 text 26 29 21 element nummer 1 27 28 26 text attribute local_name value document id owner name value 1 3 id 1 2 21 id 2 doc_id 1 1 Mustermann vorname 1 1 Max vorwahl 1 1 0511 nummer 1 1 1234567 1 1 1234567 1 Abbildung 3.15: Die endgültige Version der Datenbankstruktur 40 4 Umsetzung von XQuery–Anfragen XQuery–Anfragen an XML–Daten, die in durch ein DBMS in Datenbanken gespeichert wurden, können nicht direkt an die Daten gestellt werden, da diese in einem anderen Speicherformat vorliegen. Insbesondere, wenn es sich um relationale Datenbanken handelt, sind die Daten außerdem in ihrer Struktur verändert worden, um eine relationale Abbildung hierarchischer Datenstrukturen erreichen zu können. Welche Auswirkungen diese Abbildung auf die Struktur der Daten hat und in welcher eventuell veränderten Form die Daten vorliegen, hängt dabei von verwendeten Nummerierungsschema und der verwendeten Abbildung der Daten ab. In den folgenden Abschnitten soll die Abbildung von XML–Daten auf relationale Datenbanken das im vorangegangen Kapitel vorgestellte Modell der Vergabe von Nested Set Model Koordinaten an Knoten in einem Baum verwendet werden. Knotenrepräsentationen von XML–Knoten basieren dabei auf dem Document Object Model, das eine Standard–API des W3C bereitstellt. 4.1 Vorgehensweise und Einschränkungen bei der Umsetzung von XQuery–Anfragen Im Folgenden werden Methoden vorgestellt, wie XQuery–Ausdrücke so umgesetzt werden können, dass eine Auswertung der Ausdrücke basierend auf dem verwendeten Datenbankmodell ermöglicht wird, ohne das XML–Dokument wiederherstellen zu müssen. XPath ist als Sprachstandard, XQuery als Vorschlag eines Sprachstandards durch kontextfreie Grammatiken, angegeben in der Erweiterten Backus–Naur–Form (EBNF), spezifiziert. Diese Spezifikationen sind unter [BBC+ 04] (XPath) und [CD99] (XQuery) einsehbar, auf den Abdruck wird hier verzichtet. Im Rahmen dieser Arbeit wird nicht der komplette Sprachumfang von XQuery umgesetzt, sondern eine Teilmenge des Sprachumfangs, die sog. FLWR (For–Let–Where–Return) – Ausdrücke. FLWR–Ausdrücke sind Konstrukte, die mittels einer For–Schleife über eine bestimmte Knotenmenge iterieren. Für jeden Iterationsschritt können durch die Let–Anweisungen weiteren Variablen Werte oder Knotenmengen zugewiesen werden. Durch die Where–Klausel können die Return– Anweisungen der einzelnen Iterationen einer For–Schleife bedingt ausgeführt werden. Die Return–Klausel gibt in erster Linie Zeichenketten zurück, die ihrerseits jedoch wieder eine XML–Knotenmenge darstellen kann. Eine Einschränkung der Unterstützung auf diese Teilmenge kann durch Änderungen von ursprünglichen Produktionen der Grammatik erfolgen. Werden im Folgenden Einschränkungen an den ursprünglichen Grammatiken vorgenommen, werden hierfür neue Produktionen eingesetzt oder bestehende geeignet abgeändert. 41 4 Umsetzung Die Sprache XQuery ist äußerst komplex und bietet neben einfachen Abfragen auch Möglichkeiten zur Definition benutzerdefinierter Funktionen, eigener Elementkonstruktoren, if–then–else und andere Konstrukte, deren Implementation den Rahmen dieser Arbeit übersteigen würden. Aus diesem Grund wird die Unterstützung von Komponenten der FLWR–Ausdrücke auf einen sinnvollen Umfang eingeschränkt. For–Klauseln werden auf die Bestimmung einer Knotenmenge mit Hilfe eines Location Path und einer Zuweisung einer Laufvariablen eingeschränkt, die bei einer Iteration über eine Knotenmenge immer genau einen aktuellen Knoten enthält. Für einen FLWR– Ausdruck wird zur Vereinfachung lediglich eine For–Klausel zugelassen. Variablenzuweisungen in Let–Klauseln werden so eingeschränkt, dass diesen ausschließlich Knotenmengen bestehend aus null bis beliebig vielen Knoten sowie Literale (Zeichenketten, Zahlenwerte) zugewiesen werden können. Für die Auswahl von Knotenmengen werden Location Paths verwendet, die entweder gänzlich neue Knotenmengen aus beliebigen XML–Dokumenten auswählen oder auf bereits (durch die For–Klausel oder andere Let–Klauseln) Variablen zugewiesenen Knotenmengen basieren. Für die Zuweisung von Zeichenketten und Zahlenwerten werden direkte Zuweisungen sowie Zuweisungen von Funktionsergebnissen zugelassen. Der unterstützte Funktionsumfang wird auf Funktionen beschränkt, die Literale zurückgeben und als Eingabemenge neben Knotenmengen und Literalen keine weiteren Parameter erlauben. Where–Klauseln werden auf gängige Vergleichsoperationen wie Test auf Gleichheit und Kleiner–Größer–Beziehungen eingeschränkt, wobei diese Operationen für Vergleiche zwischen Werten (Zahlenwerte, Zeichenketten) ausgelegt werden. Die Auswahl verglichener Werte kann durch Variablenreferenzen, Funktionsergebnisse und Location Paths, die zu einzelnen Textknoten ausgewertet werden können, geschehen. Location Paths können auf bereits zugewiesenen Variablen basieren. Return–Klauseln werden so angelegt, dass sie generell eine Zeichenkette zurückgeben, die ein Auswertungsergebnis darstellt. Variablenreferenzen und darauf aufbauende weitere Location Paths werden zugelassen. Prädikate in Location Paths zur weiteren Spezifikation auszuwählender Knotenmengen werden wie die Where–Klauseln auf gängige Vergleichsoperationen zwischen Location Paths, die zu einzelnen Textknoten ausgewertet werden können, Funktionsergebnissen und Literalen eingeschränkt. Eine Behandlung eines gegebenen XQuery–Ausdrucks erfolgt anwendungsseitig durch einen XQuery–Parser, der in der Lage ist, einen Ableitungsbaum der (modifizierten) Grammatik zu erstellen. Der im Rahmen dieser Arbeit verwendete XQuery–Parser ist hierzu in der Lage und liefert einen Ableitungsbaum, der anwendungsseitig als Grundlage verwendet wird, eine Auswertung des Ausdrucks vorzubereiten und durchzuführen. Von den Möglichkeiten zur Ausführung beliebigen Java–Codes bereits während der Laufzeit des Parsing–Vorgangs, die der hier eingesetzten Parser bietet, wird vor dem Hintergrund der Aufgabentrennung kein Gebrauch gemacht, die Behandlung und Auswertung von XQuery–Ausdrücken erfolgt durch die Anwendung. Eine nähere Beschreibung des erstellten Ableitungsbaums ist im fünften Kapitel angegeben, ebenso eine näherer Beschreibung des verwendeten Parsers. 42 4 Umsetzung 4.2 Umsetzung der Datenbankstruktur Durch die folgenden SQL–Statements wird eine physische Datenbankstruktur geschaffen, die in der Lage ist, die im vorhergehenden Kapitel entwickelte logische ER–Model für DOM–Objekte und ihre Nested Set Koordinaten abzubilden. CREATE TABLE XML_NODE (DOCUMENT_ID LFT RGT NODE_TYPE LOCAL_NAME VALUE PARENT CREATE TABLE XML_ATTRIBUTE(ID OWNER NAME VALUE NUMBER(6), NUMBER(16), NUMBER(16), VARCHAR(12), VARCHAR2(4000), VARCHAR2(4000), NUMBER(16)) NUMBER(16), NUMBER(16), VARCHAR(255), VARCHAR2(4000)) ALTER TABLE XML_NODE ADD CONSTRAINT XML_PK_NODE PRIMARY KEY (LFT) ALTER TABLE XML_ATTRIBUTE ADD CONSTRAINT XML_PK_ATTRIBUTE PRIMARY KEY (ID) Die Zuweisung der Wertebereiche, in denen Attribute dieser Relationen zugelassen sind, erfolgt hier weitestgehend willkürlich. Die XML–Spezifikation sieht keine Beschränkungen in der gewählten Länge von Elementinhalten, Attributnamen und -werten etc. vor. Insbesondere sind Inhalte von Textknoten nicht in der Anzahl enthaltener Zeichen beschränkt und würden deshalb in Weiterentwicklungen eher durch BLOB–Datentypen ersetzt werden. Für die hier eingesetzten exemplarischen Beispiele sind die gewählten Datentypen und Wertebereiche jedoch durchaus ausreichend. Die in der entwickelten Datenbankstruktur bereits angedeuteten Schlüsselbeziehungen zwischen den Tupeln der einzelne Relationen werden durch die folgenden SQL– Statements erzeugt. ALTER TABLE XML_ATTRIBUTE ADD CONSTRAINT XML_FK_ATTRIBUTE FOREIGN KEY (OWNER) REFERENCES XML_NODE(ID) ON DELETE CASCADE 4.3 Umsetzung von Axis Steps in Location Paths FLWR–Ausdrücke verwenden zur Bestimmung von Knotenmengen Location Paths der Sprache XPath. Ein Pfadausdruck (PathExpr) beschreibt diesen Location Path. Ein Location Path ist eine Aneinanderreihung von Axis Steps (AxisStep), von denen jeder eine Menge von Knoten auswählt, die aus der Menge von Knoten entsteht, die durch 43 4 Umsetzung den vorhergehenden Axis Step ausgewählt wurde. Aus welcher Knotenmenge der erste Axis Step, der über keinen expliziten Vorgänger verfügt, Knoten auswählt, hängt ab von der Einleitung des Location Path sowie der Art der Anwendung, die diesen Location Path behandelt und dem Verhältnis, wie sich die Anwendung zu XML–Dokumenten oder XML–Knoten verhält bzw. in welchem Kontext sie zu diesen steht. Ein mit einem einfachen Schrägstrich "/" eingeleiteter Location Path bezieht sich auf die Knotenmenge, die ausschließlich den Wurzelknoten eines (bestimmten) XML–Dokuments enthält. Ein mit einem doppelten Schrägstrich "//" eingeleiteter Location Path bezieht sich auf alle Knoten eines (bestimmten) XML–Dokuments. Ein ohne eines dieser Symbole eingeleiteter Location Path bezieht sich auf eine aktuelle, zuvor bestimmte Knotenmenge. XSL–Transformationen bieten beispielsweise ein Konstrukt <xsl:for-each ...>, das über eine Knotenmenge iteriert. Ein ohne Schrägstriche eingeleiteter Location Path bzw. sein erster Axis Step innerhalb eines solchen Konstrukts bezieht sich dabei auf genau diese Knotenmenge als Grundmenge. PathExpr ::= ("/" RelativePathExpr?) | ("//" RelativePathExpr) | RelativePathExpr RelativePathExpr ::= StepExpr (("/" | "//") StepExpr)* Die Bestimmung eines abzufragenden XML–Dokuments geschieht in der Regel dadurch, dass der Anwendung, die XML–Daten auswertet, das aktuelle Dokument bekannt ist und dieses auswertet. Die hier entwickelten Vorgehensweisen hingegen verfügen über Zugriff auf alle in der Datenbank gespeicherten XML–Dokumente. Um einen Location Path auf ein bestimmtes XML–Dokument der Datenbank anwenden zu können, soll eine neue Produktion CallDocument definiert werden, um eine Möglichkeit schaffen, ein XML– Dokument explizit aus der Datenbank auszuwählen. Um Location Paths zuzulassen, die auf bereits durch Variablen bestimmte Knotenmengen basieren, soll zusätzlich eine neue Produktion VarrefPathExpr definiert werden. CallDocument VarrefPathExpr ::= ("doc(" | ("doc(" ::= (Varref | (Varref Literal ")/" RelativePathExpr?) Literal ")//" RelativePathExpr) ("/" RelativePathExpr?)?) "//" RelativePathExpr) Die Produktion CallDocument definiert ein Konstrukt, mit dem durch die Angabe eines eindeutigen Bezeichners doc(Bezeichner) ein XML–Dokument eindeutig aus der Datenbank ausgewählt wird. Der Einbau dieser Produktion in die Grammatik wird im Abschnitt über die FOR–Klauseln erläutert. Die Produktion VarrefPathExpr definiert ein Konstrukt, mit dem ein Location Path ausgehend von derjenigen Menge von Knoten, die durch die Variable gebunden wurden, spezifiziert werden kann. Die PathExpr eines Axis Step besteht aus einer XPath–Achse und einer Auswahl des Knotentyps, getrennt durch zwei Doppelpunkte, sowie einer angehängten Liste von Prädikaten. 44 4 Umsetzung StepExpr AxisStep ForwardStep ForwardAxis ::= ::= ::= ::= | | | | | | | ReverseStep ReverseAxis ::= ::= | | | | AbbrevReverseStep NodeTest NameTest PredicateList Predicate ::= ::= ::= ::= ::= AxisStep | FilterExpr (ForwardStep | ReverseStep) PredicateList (ForwardAxis NodeTest) | AbbrevForwardStep "child" "::" "descendant" "::" "attribute" "::" "self" "::" "descendant-or-self" "::" "following-sibling" "::" "following" "::" "namespace" "::" (ReverseAxis NodeTest) | AbbrevReverseStep "parent" "::" "ancestor" "::" "preceding-sibling" "::" "preceding" "::" "ancestor-or-self" "::" ".." KindTest | NameTest QName | Wildcard Predicate* "[" Expr "]" Die theoretischen Grundlagen, wie Navigationen entlang von XPath–Achsen durch die gewählte Indizierung mit Nested Set Koordinaten möglich sind, wurden im vorangegangenen Kapitel erläutert. Diese Grundlagen können verwendet werden, um solche Navigationen in eine geeignete Anfragesprache wie SQL umzusetzen. StepExpr – Ausdrücke werden immer ausgehend von einem bestimmten Knoten eines sog. Knotenkontextes ausgewertet. Ein Knoten ist dabei immer Teil einer geordneten Menge von Knoten, die sich in seinem Kontext befinden. Es soll im Folgenden davon ausgegangen werden, dass das Tupel, das einen Kontextknoten repräsentiert, ein Tupel einer Node–Relation tmp1 ist. Da StepExpr – Ausdrücke hintereinandergehängt werden können und Auswertungsergebnisse auch die Eingangsmenge eines folgenden Ausdrucks sein können, sollte die Repräsentation eines Auswertungsergebnisses von derselben Form sein wie die Eingangsmenge, aus der eine neue Tupelmenge gewonnen wird, um umgesetzte StepExpr – Ausdrücke rekursiv verwenden zu können. Weiterhin wird zur Vereinfachung davon ausgegangen, dass sich in der Datenbank lediglich ein XML–Dokument befindet, um hier unnötige Einschränkungen auf eine bestimmte Dokumenten–ID einzusparen. Die Auswahl von Knoten geschieht in der Auswertung von XPath–Location–Paths immer aus der gesamten zur Verfügung stehenden Knotenmenge, unter der getroffenen Voraussetzung also aus allen Tupeln der Relation Node. Es werden also für die Auswertung einer StepExpr in der Regel ein Verbund von zwei Relationen benötigt. Eine Relation tmpx beinhaltet dabei die Tupel, die in der Ausgangsmenge liegen. Eine Tupelmenge tmpx+1 repräsentiert die Ergebnismenge der 45 4 Umsetzung Auswertung des Ausdrucks. Es werden nun einige SQL–Umsetztungen einer Navigation entlang der Hauptachsen von XPath erläutert. Die Umsetzung der Achse descendant Die XPath–Achse descendant wählt alle Knoten aus, die Nachfahren eines Knoten des Kontextes (tmp1) sind. Für diese Knotenmenge wurde die Erkenntnis gewonnen, dass diese Knoten die Eigenschaft haben, dass ihre Koordinaten innerhalb eines Intervalls liegen, das durch die Koordinaten ihrer Vorfahren begrenzt wird. Hier und in den folgenden Abschnitten werden lediglich die Relationen eines einzigen Axis Step erläutert, so dass die Ergebnismengen noch keine eigenen Namen erhalten. Die ORDER BY – Anweisung zur Sortierung der Tupelmenge nach der linken Koordinate ordnet die repräsentierten XML–Elemente nach der Dokumentenordnung, in der sie sich ursprünglich befanden, da sie auch in dieser Reihenfolge indiziert wurden. SELECT FROM WHERE ORDER BY node.* tmp1, node node.lft > tmp1.lft AND node.rgt < tmp1.rgt node.lft Die Umsetzung der Achse ancestor Die XPath–Achse ancestor wählt alle Knoten aus, die Vorfahren des aktuellen Kontextknoten sind. Wie bereits festgestellt wurde, gilt dieselbe Erkenntnis wie bei descendant mit vertauschten Rollen der Knoten. Ein Knoten A liegt in der Menge ancestor eines Knoten B, wenn B in der Menge descendant von A liegt. Die SQL–Anfrage lässt sich also ganz ähnlich formulieren. SELECT FROM WHERE ORDER BY node.* tmp1, node tmp1.lft > node.lft AND tmp1.rgt < node.rgt node.lft Die Umsetzung der Achse following Die XPath–Achse following wählt alle Knoten aus, die in Dokumentenordnung auf einen aktuellen Kontextknoten folgen. In einer Baumstruktur wählt diese Achse alle Knoten aus, die rechte Nachbarn oder rechte Nachbarn eines Vorfahren des Kontextknoten sind. In der Dokumentenstruktur handelt es sich dabei um alle Elemente, die durch ein öffnendes Tag in Dokumentenreihenfolge nach dem schließenden Tag des aktuellen Elements beginnen. Für diese Knoten gilt, dass ihre linke Seite (Eulerscher Umlauf) bzw. ihr Starttag (Zeichenkettenstrom) indiziert werden, nachdem die rechte Seite des aktuellen Knoten abgearbeitet wurde, denn dann sind die Koordinaten des später indizierte Elements nicht in dem Intervall enthalten, das die Koordinaten des aktuellen Kontextknoten begrenzen und damit eindeutig von den hierarchischen ancestor- und descendant–Achsen unterscheidbar. 46 4 Umsetzung SELECT FROM WHERE ORDER BY node.* tmp1, node node.lft > tmp1.rgt node.lft Da eine linke Seite eines Knoten immer vor seiner rechten Seite indiziert wird, gilt für jeden Knoten node.lf t < node.rgt. Die Knotenmengen, die sich in Dokumentenordnung in der Richtung unterscheiden, lassen sich eindeutig durch die linke Untergrenze bzw. die rechte Obergrenze der jeweiligen Menge unterscheiden. Die Umsetzung der Achse preceding Die XPath–Achse preceding wählt alle Knoten aus, die in Dokumentenordnung vor einem aktuellen Kontextknoten vorgekommen sind. Ähnlich wie bereits bei der Ähnlichkeitsbeziehung der Vorgehen zur Bestimmung der hierarchisch unterscheidbaren Mengen verhalten sich die Mengen preceding und following zueinander. Ein Knoten A liegt in der Menge, die durch preceding ausgehend von einem Knoten B ausgewählt wird, wenn B in der Menge der Nachfolger von A liegt. Diese Menge kann also ebenfalls mit vertauschen Rollen der Knoten wie die Menge following bestimmt werden SELECT FROM WHERE ORDER BY node.* tmp1, node tmp1.lft > node.rgt node.lft Die Umsetzung der Achse following-sibling Die XPath–Achse following-sibling wählt alle Knoten aus, die auch durch die Achse following ausgewählt wird mit der Einschränkung, dass die ausgewählten Knoten über denselben Elternknoten verfügen. Die bereits entwickelte Anfrage following-Anfrage, erweitert um eine entsprechende Einschränkung, kann diese Menge auswählen. SELECT FROM WHERE ORDER BY node.* tmp1, node node.lft > tmp1.rgt AND node.parent = tmp1.parent node.lft Die Umsetzung der Achse preceding-sibling Die XPath–Achse preceding-sibling wählt alle Knoten aus, die auch durch die Achse preceding ausgewählt wird mit derselben Einschränkung wie oben beschrieben. SELECT FROM WHERE ORDER BY node.* tmp1, node node.lft > tmp1.rgt AND node.parent = tmp1.parent node.lft 47 4 Umsetzung Die Umsetzung der Achse children Die XPath–Achse children wählt alle Knoten aus, die auch durch die Achse descendant ausgewählt wird mit der Einschränkung, dass die ausgewählten Knoten den Kontextknoten als gemeinsamen Elternknoten haben. Die Eigenschaft des Kontextknoten als gemeinsamer Elternknoten der Knoten der ausgewählten Menge genügt als Kriterium der Zuordnung zu dieser Menge. SELECT FROM WHERE ORDER BY node.* tmp1, node node.parent = tmp1.parent node.lft Die Umsetzung der Achse parent Die XPath–Achse parent wählt alle Knoten aus, die Elternknoten des aktuellen Kontextknoten ist. Diese Information wurde im Rahmen des Datenbankentwurfs bereits als Teil der Tupelattribute berücksichtigt, so dass ein direkter Zugriff auf den Knoten möglich ist, der diese Eigenschaft erfüllt. SELECT FROM WHERE node.* tmp1, node node.id = tmp1.parent Die Umsetzung der Typeinschränkung Ein Axis Step in XPath besteht neben der Angabe der Achse auch aus einem Typausdruck, der die Menge, die die gewählte Achse beschreibt, auf Knoten dieses Typs beschränkt. KindTest ::= | | | | | AnyKindTest TextTest CommentTest PITest ::= ::= ::= ::= AttributeTest AttribNameOrWildcard AttributeDeclaration ElementTest ElementNameOrWildcard ::= ::= ::= ::= ::= ElementTest AttributeTest PITest CommentTest TextTest AnyKindTest "node" "(" ")" "text" "(" ")" "comment" "(" ")" "processing-instruction" "(" (NCName | StringLiteral)? ")" "attribute" "(" AttribNameOrWildcard ")" AttributeName | "*" AttributeName "element" "(" ElementNameOrWildcard ")" ElementName | "*" 48 4 Umsetzung Die Knotentypen wurden bereits in dem Datenbankentwurf berücksichtigt und so können Typeinschränkungen leicht in die WHERE–Klauseln der bereits entwickelten SQL– Statements integriert werden. Funktionsaufrufe, die die Menge ausschließlich durch den Typ einschränken (text(), comment() oder node()) können durch einen Zusatz zu den bereits entwickelten WHERE–Klauseln in der Art ... WHERE (...) AND node.type = Knotentyp ... wobei Knotentyp eine Konstante ist, die den entsprechenden Knotentyp identifiziert. Eine Alternative zu einer nachträglichen Einschränkung der Ergebnismenge auf Knotentyp ist die Einschränkung der zugrundeliegenden Menge an Knoten auf Knoten der in Frage kommenden Typen bereits im voraus. Besonders, da die bisher entwickelten SQL– Statements für jeden Axis–Step einen Join von zwei Relationen vorsehen, kann sich die Einschränkung der Grundmenge in einer konkreten Implementation günstiger auswirken. Die Eingabemenge enthält zwar bislang nach Voraussetzung lediglich ein Tupel, aber hinsichtlich höherer Anforderungen sollten auch für Mengen von Tupeln Join günstig ausgeführt werden können, indem die Grundmenge der Knoten, mit der ein Join der Eingabemenge durchgeführt wird, verkleinert wird. ... FROM tmp1, (SELECT * FROM node WHERE type = Knotentyp) AS node ... Spezifischere Typprüfungen sind processing–instruction(), attribute() und element(), denen ein Parameter für die Prüfung übergeben wird. element(para) schränkt die Grundmenge auf alle Knoten ein, die von dem Typ "Element" sind und deren Tagname mit para übereinstimmt. Der Datenbankentwurf sieht vor, dass die Tagnamen–Eigenschaft eines Elements direkt als Eigenschaft eines Tupels der Relation Node abgebildet wird. Die Typprüfung element(para) kann also durch die Inline View ... FROM tmp1, (SELECT * FROM node WHERE local_name = ’para’) AS node ... bereits implizit vorgenommen werden, da ausschließlich Elementknoten über einen Wert in ihrem Attribut local_name verfügen können. Anfragen an bestimmte processing-instructions sollen hier außer Acht gelassen werden, da sie und ihre Bearbeitung nicht Gegenstand dieser Arbeit ist. Eine ähnliche 49 4 Umsetzung Einbindung dieser Knoten analog zu der im folgenden beschriebenen ist jedoch jederzeit möglich. Attributknoten werden nach dem entwickelten Datenbankentwurf in einer Relation gespeichert, die anders als andere Knotentypen zwar den Knoten referenzieren können, zu dem sie gehören, sie werden aber nicht als Knoten des DOM–Baums behandelt und es besteht nur eine einseitig gerichtete Kinderknoten–Elternknoten–Beziehung (genauer: Kinderknoten–Besitzerknoten–Beziehung) zwischen Attributknoten und Knoten des DOM–Baums. ... FROM tmp1, (SELECT FROM WHERE AND AND ... * node, attribute attribute.owner = node.id attribute.name = Attributname attribute.value = Attributwert) AS node 4.4 Umsetzung von Location Paths Bislang wurde vorgestellt, wie einzelne Axis Steps in SQL–Statements umgesetzt werden können. Ein Location Path ist eine Aneinanderreihung von einzelnen Axis Steps. Location Paths werden von links nach rechts ausgewertet. Ein Axis Step liefert eine Menge von Knoten, die einen sogenannten Kontext bilden. Die Auswertung eines folgenden Axis Step erfolgt auf der Grundlage der Ergebnismenge des vorhergenden. Die Umsetzung einzelner Axis Steps wurde so entwickelt, dass ihre Ausgabemenge strukturell ihrer Eingabemenge entspricht. So lassen sich diese Umsetzungen schachteln. Strukturell sehen SQL– Anweisungen, die Navigationen von Axis Steps der Form Achsenbedingung::Knotentyp modellieren, immer gleich aus. SELECT node.* FROM tmp1, (SELECT * FROM node WHERE type = Knotentyp) AS node WHERE Achsenbedingung Dieses SQL–Statement kann als eine Funktion f (Achsenbedingung, Knotentyp, Eingabemenge) = Ausgabemenge betrachtet werden, wobei die Eingabemenge durch tmp1 beschrieben ist. Dementsprechend soll die Ausgabemenge als tmp2 bezeichnet werden. Eingabe- und Ausgabemenge sind von derselben Form, verfügen also insbesondere über dieselben Attributmengen. So kann eine Ergebnismenge eines SQL–Statements wieder als Eingabemenge eines nächsten Axis Step dienen. Im allgemeinen soll im folgenden eine Eingabemenge als tmpx und die entsprechende Ausgabemenge als tmpx+1 bezeichnet werden. 50 4 Umsetzung Eingabemenge Axis Step als Einschränkung des Kreuzverbunds von Eingabemenge und eingeschränkter Knotengrundmenge tmpx tmpx node Ausgabemenge tmpx+1 u SELECT ... ... AS tmpx SELECT DISTINCT node.* FROM tmpx,(SELECT * FROM node WHERE type = Knotentyp) AS node WHERE Achsenbedingung AS tmp{x+1} Abbildung 4.1: XPath–Achsen als Black Boxes Eine Aneinanderreihung von Axis Steps, bei der ein folgender Axis Step immer auf der Grundlage der Knotenmenge, die ein vorhergender Axis Step bestimmt hat, ausgewertet wird, kann durch die vorgestellten SQL–Statements abgebildet werden, indem die temporären Grundmengen von Knoten, die durch ein vorhergehendes SQL–Satement bestimmt wurden, als Inline View in die FROM–Klausel des nachfolgenden SQL–Statements geschachtelt wird. Das folgende Beispiel soll dieses Vorgehen und den Aufbau der Schachtelung verdeutlichen. .../descendant::element(elem1)/following::element(elem2) Der durch ... angedeutete einleitende Kontext in Form einer Sicht auf eine Tupelmenge der Relation Node sei dabei als tmp1 vorhanden. Der erste Axis Step des Location Path navigiert entlang der descendant – Achse und kann durch das SQL–Statement SELECT FROM WHERE ORDER BY node.* tmp1, node node.lft > tmp1.lft AND node.rgt < tmp1.rgt node.lft abgebildet werden. Die Einschränkung des Knotentyps auf Elementknoten mit dem Tagnamen "elem1" kann in der From–Klausel durch die Verwendung einer Inline View auf eine eingeschränkte Grundmenge an Knoten berücksichtigt werden. SELECT FROM node.* tmp1, (SELECT * FROM node 51 4 Umsetzung WHERE local_name = ’elem1’) AS node WHERE node.lft > tmp1.lft AND node.rgt < tmp1.rgt ORDER BY node.lft Die Abfrage liefert einen Ausschnitt der Relation Node, der die Knoten repräsentiert, die in der Menge liegen, die durch die Auswertung des ersten Axis Step ausgewählt wird. Das Ergebnis dieser Abfrage sei jetzt als tmp2 bezeichnet. Der folgende Axis Step bewegt sich entlang der Achse following und wählt Elementknoten mit dem Namen "elem2" aus. Diese Auswahl kann in SQL wie folgt beschrieben werden. SELECT FROM node.* tmp2, (SELECT * FROM node WHERE local_name = ’elem2’) AS node WHERE node.lft > tmp2.rgt ORDER BY node.lft Die temporäre Relation tmp2, die die Grundmenge an Knoten beschreibt, die in der Menge des ersten Axis Step verblieben sind, kann an dieser Stelle auch direkt zur Laufzeit als Inline View der zweiten Abfrage erzeugt werden, da diese Daten an keiner anderen Stelle benötigt werden. Dies geschieht durch die Schachtelung der Abfrage des ersten Axis Step in die FROM–Klausel der zweiten Abfrage. SELECT FROM node.* (SELECT FROM node.* tmp1, (SELECT * FROM node WHERE local_name = ’elem1’) AS node WHERE node.lft > tmp1.lft AND node.rgt < tmp1.rgt ORDER BY node.lft) AS tmp2, (SELECT * FROM node WHERE local_name = ’elem2’) AS node WHERE node.lft > tmp2.rgt ORDER BY node.lft Durch eine rekursive Schachtelung dieser Anfrage als Grundmenge einer neuen Anfrage können weitere Axis Steps abgebildet werden. Der Beginn des Location Path wurde in diesen Beispielen bislang ausgelassen. Der erste Axis Step eines Location Path verfügt wie jeder andere auch über eine Grundmenge, einen Kontext. Die Spezifikation der Grammatik sieht drei Möglichkeiten vor, wie die Grundmenge an Knoten bestimmt werden kann, von dem der Pfad beginnt. Die Auswahl aller Knoten eines XML–Dokuments geschieht durch "//", der Wurzel- bzw. Dokumentknoten geschieht durch "/" und die Auswahl des aktuellen Kontext wird durch das 52 4 Umsetzung Weglassen dieser Symbole eingeleitet. Das Vorliegen eines aktuellen Kontext kann in bestimmten Anwendungen von XPath vorkommen, wie XSL–Transformationen, die ihrerseits über einen bestimmten Kontext von XML–Dokumenten verfügen. Die Auswertung von XQuery–Anfragen, so wie sie hier behandelt werden, stehen nicht in einem Kontext zu XML–Dokumenten und werden "von außen" ausgewertet. Aus diesem Grund wird der Bezug auf einen aktuellen Kontext hier nicht behandelt. Der Bezug auf den Wurzelknoten oder alle Knoten eines XML–Baums setzt jeweils voraus, dass das XML–Dokument, auf das sich bezogen wird, bekannt ist. In dem entwickelten Datenbankschema können einzelne XML–Dokumente durch eine eindeutige doc_id unterschieden werden. Ein AxisStep, der mit einem CallDocument – Ausdruck doc("doc_id")/ eingeleitet wird, bezieht sich dabei auf eine Relation tmp1 als Grundmenge, die aus dem folgenden SQL–Statement entsteht. SELECT FROM WHERE ORDER BY * node doc_id = ’doc_id’ AND node_type = ’document’ node.lft Die Grundmenge für den CallDocument – Ausdruck doc("doc_id")// wird durch eine geänderte WHERE–Klausel gebildet. WHERE doc_id = ’doc_id’ 4.5 Funktionsaufrufe Eigene Funktionsdefinitionen sollen, wie bereits erwähnt, hier nicht behandelt werden. Die Umsetzung von Funktionen, die Operationen auf Knoten und Knotenmengen soll hier an einem ausgewählten Beispiel der XPath–Funktionsbibliothek gezeigt werden, die auch in XQuery zur Verfügung steht. Diese Funktion liefert einen Zahlenwert als Ergebnisse zurück. Andere Funktionen bieten grundsätzliche Manipulationsmöglichkeiten für diese Datentypen. Ein Teil dieser Funktionen ist so grundlegend, dass sie durchaus durch ein DBMS ausgewertet werden können (Konkatenation von Zeichenketten, Summierung von Zahlenwerten, einfache mathematische Operationen), ein anderer Teil dieser Funktionen dagegen sind komplexer und lassen sich nicht ohne weiteres in SQL–Statements einbauen (Normalisierung von Whitespaces, Umsetzen von Teilzeichenketten auf andere Zeichenketten). Um diese Funktionen bereitstellen zu können, können Funktionen könnten anwendungsseitig ausgewertet werden. Dies ist sinnvoll, da so die Grenzen der umsetzbaren Funktionen nicht abhängig von den Grenzen des verwendeten RDBMS, sondern von den Grenzen der verwendeten Programmiersprache, die im allgemeinen umfassender sind, zu machen. Im Wesentlichen können zwei Klassen von Funktionen unterschieden werden. Eine Klasse von Funktionen erhält als Eingabeparameter Zahlen oder Zeichenketten, eine weitere Klasse von Funktionen erhält als Eingabeparameter Knotenmengen. Funktionen, die als Eingabaparameter Zahlen– oder Zeichenkettenwerte erhalten, können durch 53 4 Umsetzung höhere Programmiersprachen wie Java in der Regel leicht umgesetzt werden und können beispielsweise als statische Methoden einer Bibliotheksklasse implementiert werden. Funktionen, die Knotenmengen als Eingabeparameter erhalten, müssen so implementiert werden, dass sie Operationen auf diesen Knotenmengen ausführen und Anfragen selbstständig auswerten können. Eine Methode, die auf Knotenmengen operiert und häufig verwendet wird, ist die count() – Methode. Diese Methode soll hier als Grundlage der Erläuterung dienen, wie solche Methoden implementiert werden können. Diese Methode zählt alle Knoten, die in einem NodeSet vorkommen. Es ist an dieser Stelle zu beachten, dass hier im Zusammenhang mit "Knoten" Strukturelement–Knoten gemeint sind, also Knoten vom Typ Element. Dieser Funktion wird eine Knotenmenge übergeben. Knotenmengen wurden bislang als Tupel einer SQL–Anfrage betrachtet. Diese Tupel sind in Java über die Klasse ResultSet ansprechbar. In Java können Objekte dieser Klasse zwischen Methoden ausgetauscht werden. Einer Umsetzung der count–Methode kann also ein ResultSet übergeben werden, um die Methode auszuwerten. Das übergebene ResultSet wird dabei nicht weiter verändert. Eine Implementation dieser Methode muss also über dieses ResultSet iterieren, um die Anzahl auftretender Elementknoten zu zählen. Eine geschicktere Implementation übergibt dieser Methode nicht das ResultSet, das eine Knotenmenge beinhaltet, da ResultSets nicht weiter verändert werden können, sondern überlässt es auch in Hinblick auf eine Erweiterbarkeit der Menge umsetzbarer Knotenmengenfunktionen der Implementation einer Methode, wie Knotenmengen ausgewertet und ggf. manipuliert werden. Hierzu müssen sich diese Methoden ein eigenes ResultSet schaffen. Die count–Funktion zählt alle Knoten des Typs Element, die in einer gegebenen Knotenmenge (bezeichnet als tmp) auftauchen. SELECT count(*) FROM tmp WHERE node_type = ’element’ Die bezogene Knotenmenge, aus der Tupel gewählt werden, liegt, sofern sie vorher einer Variablen zugeordnet wurde, in einer temporären Ergebnisrelation result vor und sind mit dem Attribut result_id eindeutig einem Variablennamen zugeordnet. Eine Übergabe des Variablennamen (im Folgenden als $var bezeichnet) kann also für die count–Funktion ausreichend sein, um eine eigene SQL–Anfrage wie auf Tupelmengen auszuführen. SELECT FROM WHERE AND count(*) xml_result node_type = ’element’ result_id = $var Um auch Auswertungsergebnisse aus Knotenmengen gewinnen zu können, die nicht durch eine Variablenzuweisungen explizit in die Ergebnisrelation ausgewählt wurden, muss die bisher entwickelte Methode, Knotenmengen Variablen zuzuweisen, wenn dies 54 4 Umsetzung durch eine LET–Klausel gefordert wird, so erweitert werden, dass Knotenmengen, wenigstens wenn sie Eingabemengen von Funktionen darstellen, immer in die Ergebnisrelation gespeichert und über einen generisch vergebenen Variablennamen zugänglich gemacht werden, so dass auch solche Knotenmengen von Funktionen über einen Variablennamen ansprechbar sind, der ihnen übergeben wird. 4.6 FLWR–Ausdrücke in XQuery–Anfragen FLWR (For–Let–Where–Return) – Ausdrücke bestehen aus vier verschiedenen Komponenten, die im Folgenden näher beschrieben werden. Hauptsächlich stellt eine FLWR–Ausdruck eine Möglichkeit bereit, aus einer Menge von XML–Knoten eine Teilmenge auszuwählen und als neues (XML–) Dokument zur Verfügung zu stellen. FLWR–Ausdrücke wählen hierzu aus einer Menge von XML– Knoten Teilmengen aus oder konstruieren eine neue Menge von XML–Knoten. 4.6.1 Umsetzung der FOR–Klausel Die zu diesem Zeitpunkt aktuelle Spezifikation des Working Draft zu XQuery 1.0 sieht die folgenden EBNF–Ausdrücke zur Definition der Syntax einer FOR–Klausel in FLWR– Ausdrücken vor. ForClause ::= PositionalVar SimpleForClause ::= ::= "for" "$" VarName TypeDeclaration? PositionalVar? "in" ExprSingle ("," "$" VarName TypeDeclaration? PositionalVar? "in" ExprSingle)* "at" "$" VarName "for" "$" VarName "in" ExprSingle ("," "$" VarName "in" ExprSingle)* Ausdrücke wie TypeDeclaration und PositionalVar sind für spezielle Anwendungsfälle vorgesehen. Sie sollen hier nicht näher behandelt werden. Der ExprSingle–Ausdruck bezieht sich in FLWR–Ausdrücken sinnvollerweise auf Knoten eines bestimmten XML– Dokuments. Aus diesem Grund sollen diese grammatikalischen Beschreibungen durch die Verwendung der vorher beschrieben geänderten PathExpr entsprechend angepasst werden, so dass immer ein Kontext von XML–Knoten eines Dokuments geschaffen wird. ForClause SimpleForClause ::= ::= SimpleForClause "for" "$" VarName "in" PathExpr So wie die PathExpr zuvor neu spezifiziert wurde, hat dieser Ausdruck einen Rückgabewert von wenigstens einer bis zu beliebig vielen bestimmten Knotenmengen. Jede dieser Knotenmengen sollte bzw. muss über einen bestimmten eindeutigen Variablennamen ansprechbar sein. Eine Umsetzung dieser Eigenschaft kann einfach erreicht werden, indem jeder einzelne Rückgabewert einer einzelnen PathExpr, der eine Knotenmenge und damit ein Result Set einer SQL–Anfrage darstellt, in einer Ergebnisrelation abgebildet wird. Hierzu sind verschiedene Varianten denkbar. 55 4 Umsetzung Ein Result Set als Ergebnis einer PathExpr ist immer von der gleichen Struktur, die der Relation Node entspricht. Damit kann die entwickelte Datenbankstruktur entsprechend um eine Ergebnisrelation erweitert werden. In diese Relation können Primärschlüssel von result id varname node_id Abbildung 4.2: Erweiterung um eine Ergebnisrelation Knoten der Node–Relation selektiert werden, die mit einem Variablennamenattribut versehen werden. Die feste Einbeziehung dieser Ergebnisrelation ermöglicht es Knotenmengen bestimmte Bezeichner zuzuordnen, um weitere Anfragen auf Variablen anwenden zu können. Primärschlüssel dieser Relation ist das Attribut id, Fremdschlüssel das Attribut node_id. Das id – Attribut kann je nach verwendetem RDBMS auf unterschiedliche Arten vergeben werden. Oracle stellt für solche Anwendungsfälle Sequenzen bereit. Eine weitere Variante wäre die Erzeugung von Views auf die Node–Relation, die als Namen die Variablennamen erhalten. Eine generische Erzeugung und Benennung von Views kann allerdings zu Konflikten mit eventuell bereits bestehenden Views aus anderen Anwendungen führen, so dass sich im weiteren Verlauf für die Variante der fest integrierten Ergebnisrelation entschieden wird. 4.6.2 Umsetzung der LET–Klausel Die LET–Klausel wird durch das W3C ähnlich wie die For–Klausel wie folgt spezifiziert. LetClause ::= "let" "$" VarName TypeDeclaration? ":=" ExprSingle ("," "$" VarName TypeDeclaration? ":=" ExprSingle)* Es sollen hier ähnliche Einschränkungen der Spezifikation wie bei der FOR–Klausel vorgenommen werden. Erweitert werden soll die Spezifikation der LET–Klausel jedoch um die Möglichkeit, auch Zahl- oder Zeichenkettenwerte referenzierbar zu machen. LetClause ::= Literal ::= NumericLiteral ::= StringLiteral ::= ("let" "$" VarName ":=" PathExpr ("," "$" VarName ":=" PathExpr)*) | Literal NumericLiteral | StringLiteral IntegerLiteral | DecimalLiteral | DoubleLiteral (’"’ Char* ’"’) | ("’" Char* "’") Eine entsprechende Unterscheidung des Typs von Variablenzuordnungen kann anwendungsseitig durch geeignete Bibliotheken oder andere Datenstrukturen erfolgen, die geeignet sind, entsprechend unterschiedliche Datenstrukturen von Variablendeklarationen zu behandeln. 56 4 Umsetzung 4.6.3 Umsetzung der WHERE–Klausel Die Syntax der WHERE–Klausel wird im wesentlichen durch die folgenden EBNF– Ausdrücke beschrieben. Einige Regeln wurden aus Grünen der besseren Lesbarkeit ausgelassen. WhereClause Expr ... AndExpr ComparisonExpr ::= ::= ::= ::= ::= "where" Expr ... AndExpr ComparisonExpr ( "and" ComparisonExpr )* RangeExpr (((ValueComp | GeneralComp | NodeComp) RangeExpr) | FTContains)? RangeExpr ::= AdditiveExpr ( "to" AdditiveExpr )? AdditiveExpr ::= ... ... ::= ValueExpr ValueExpr ::= ValidateExpr | PathExpr | AxisStep PathExpr ::= ... ... ::= RelativePathExpr RelativePathExpr ::= StepExpr (("/" | "//") StepExpr)* StepExpr ::= AxisStep | FilterExpr FilterExpr ::= PrimaryExpr PredicateList PrimaryExpr ::= Literal | VarRef | ParenthesizedExpr | ContextItemExpr | FunctionCall | Constructor ValueComp ::= "eq" | "ne" | "lt" | "le" | "gt" | "ge" FunctionCall ::= (<QName "("> | <"id" "("> | <"key" "(">) (ExprSingle ("," ExprSingle)*)? ")" Es soll hier beispielhaft vorgestellt werden, wie WHERE–Klauseln umgesetzt werden können. Als Beispiele soll im Folgenden eine eingeschränkte Ausprägung dieser Klauseln verwendet werden, die durch die folgenden einschränkenden EBNF–Ausdrücke beschrieben werden. WhereClause Expr AndExpr ComparisonExpr RangeExpr RelativePathExpr StepExpr FilterExpr PrimaryExpr ValueComp ::= ::= ::= ::= ::= ::= ::= ::= ::= ::= "where" Expr ... ::= AndExpr ComparisonExpr RangeExpr GeneralComp RangeExpr AdditiveExpr ... ::= RelativePathExpr StepExpr FilterExpr PrimaryExpr Literal | VarRef | FunctionCall "eq" | "ne" | "lt" | "le" | "gt" | "ge" WHERE–Klauseln dieser Form bieten Vergleichsmöglichkeiten für Variablenreferenzen, weitere Navigationen in Knotenmengen, die durch Variablen beschrieben werdenund 57 4 Umsetzung Vergleiche von Funktionsergebnissen oder Elementinhalten mit bestimmbaren Werten. Durch die Hinzunahme der bereits oben angepassten PathExpr und RelativePathExpr können hier auch Daten anderer XML–Dokumente referenziert werden. PathExpr ::= (CallDocument "/" RelativePathExpr?) | (CallDocument "//" RelativePathExpr) | RelativePathExpr RelativePathExpr ::= StepExpr (("/" | "//") StepExpr)* CallDocument ::= "doc" "(" IntegerLiteral ")" StepExpr ::= FilterExpr 4.6.4 Umsetzung der RETURN–Klausel Die Syntax von RETURN–Klauseln wird durch keinen expliziten EBNF–Ausdruck beschrieben. Eine implizite Syntaxbeschreibung wird durch den folgenden Ausdruck beschrieben. FLWORExpr ::= (ForClause | LetClause)+ (ForClause | LetClause) WhereClause? "return" ExprSingle Der ExprSingle – Ausdruck ist ein sehr allgemeiner Ausdruck, in dem neben einzelnen Konstrukten wie Location Paths oder Funktionsaufrufe auch wieder ganze FLWORExpr zugelassen sein können. Eine RETURN–Klausel wird häufig als ein neues XML– Fragment definiert, deren Inhalte aus den Knoten entstehen, über die durch die FOR– Klausel iteriert wird. Zu diesem Zweck enthält eine RETURN–Klausel Zeichenketten, um neue Elemente oder feste Inhalte von Elementen zu definieren sowie Variablenreferenzen und Funktionsaufrufe, die sich auf XML–Dokumente oder vorher zusammengestellte Knotenmengen beziehen. Diese werden von den ansonsten beliebig wählbaren Zeichenketten durch einen Klammerung mit geschweiften Klammern als sogenannte Enclosed Expressions getrennt. Zur Vereinfachung soll eine eigene Definition einer RETURN– Klausel geschaffen werden, die diese Anwendung unterstützt. Unter Verwendung der bereits für die speziellen Zwecke dieser Arbeit angepassten PrimaryExpr kann die neu geschaffene ReturnClause leicht spezifiziert werden. EnclosedExpr ::= PrimaryExpr ::= FLWORExpr ::= ReturnClause ::= "{" (Expr | PathExpr) "}" Literal | VarRef | FunctionCall (ForClause | LetClause)+ (ForClause | LetClause) WhereClause? "return" ReturnClause (Char* "{" VarrefPathExpr | CallDocument | FunctionCall "}")* Char* Auswertungsergebnisse von VarrefPathExpr, FunctionCall und CallDocument sind Literale, Variablenreferenzen oder neue Knotenmengen, 58 4 Umsetzung Die Darstellung eines Literals ist ohne weiteres möglich, da diese lediglich Zeichenketten oder Zahlenwerte darstellen. Funktionen können prinzipiell neben Wahrheitswerten, Zahlenwerten und Zeichenketten auch ganze Knotenmengen zurückgeben. Die hier angesprochenen Funktionen sollen auf gängige XPath–Funktionen, die auch in XQuery zur Verfügung stehen, beschränkt werden. Diese Funktionen geben ausschließlich Zahlenwerte und Zeichenketten zurück. Variablenreferenzen können nicht ohne weiteres dargestellt werden, da diese, sofern sie nicht ihrerseits lediglich einen Zahlenwert oder eine Zeichenkette darstellen, eine bestimmte Menge von Knoten bezeichnen, die durch eine Tupelmenge gegeben ist. Bei diesen Tupeln handelt es sich um Abbildungen von XML–Elementen. Werden diese Tupel referenziert, müssen diese wieder als XML–Elemente dargestellt werden. Diese Rekonstruktion von XML–Fragmenten kann anwendungsseitig oder durch das RDBMS erfolgen. Eine Rekonstruktion durch eine Anwendung erfordert eine Implementierung des Algorithmus, mit dem ein XML–Baum zerlegt und in der Datenbank gespeichert wurde, in umgekehrter Richtung. Eine Alternative nutzt die Eigenschaft der zerlegten XML–Bäume, in Dokumentenordnung abgelegt und indiziert worden zu sein und damit die schnelle Wiederherstellbarkeit der Dokumentenreihenfolge, die in den Koordinaten direkt abgebildet wurde. Ein Tupel der Relation Node verfügt über zwei Koordinaten–Attribute. Die linke Koordinate bezeichnet eine Indexposition, an der der Knoten von links besucht wurde, die rechte Koordinate entsprechend die Indexposition, an der der Knoten von rechts besucht wurde. Ein Knoten wird erst von rechts besucht, wenn alle seine Nachfahren von links und rechts besucht wurden, so wie es im vorhergenden Kapitel beschrieben wurde. In XML–Dokumenten tauchen Strukturelemente als öffnendes und schließendes Tag auf. Diese doppelten Vorkommen von Elementen können durch eine Vereinigung zweier Auswahlen von Knotenmengen erreicht werden. SELECT * FROM (SELECT node.local_name, node.lft AS coord FROM (SELECT * FROM node WHERE (node.node_type = ’element’ OR node.node_type = ’text’)) UNION SELECT node.tag, node.rgt AS coord, ’’ AS content FROM (SELECT * FROM node WHERE node.node_type = ’element’)) ORDER BY coord Diese SQL–Statement wählt alle Knoten eines gespeicherten XML–Dokuments aus, die vom Typ Element oder vom Typ Text sind. Andere Knotentypen sollen hier aus Gründen der Übersicht ausser Acht gelassen werden, können jedoch ebenso mit ausgewählt und dargestellt werden. Die ORDER–BY–Klausel sortiert diese Knoten nach den linken und rechten Koordinaten der vereinigten Knotenmengen, die einheitlich als coord 59 4 Umsetzung bezeichnet werden. In die erste Vereinigungsmenge werden sowohl Element– als auch Textknoten einbezogen, in die zweite Vereinigungsmenge ausschließlich Elementknoten. So kann erreicht werden, dass nur doppelt auftretende Elementknoten, die durch zwei Tags dargestellt werden, doppelt ausgewählt werden. Die einfache Auswahl der Textknoteninhalte bezieht Inhalte von Textelementen einfach ein. Eine Sortierung nach linken und rechten Koordinaten stellt sicher, dass die linke Seite (das öffnende Tag) eines Elementknoten vor seiner rechten Seite (das schließende Tag) in der sortierten Relation auftaucht und dass alle Nachfahren eines Elementknoten (weitere Elementknoten oder Textknoten) in der sortierten Relation zwischen dem öffnenden und dem schließenden Elementknoten auftauchen. Diese Anfrage liefert also Mengen von Strukturelementen von XML–Dokumenten, so wie sie in Dokumentenreihenfolge korrekt angeordnet sind. Eine Aufbereitung der Tagnamen durch die Vergaben von "<", "</" und ">", so dass xml_node tmp lft 5 6 8 9 rgt 12 7 11 10 tag elem1 coord 5 6 8 9 10 12 lft 5 6 8 9 9 5 rgt 12 7 11 10 10 12 text text1 elem2 text2 tag <elem1> text text1 <elem2> text2 </elem2> </elem1> diese bereits durch diese Abfrage in der korrekten Form als öffnendes oder schließendes Tag vorliegen, ist leicht durch die folgende Abwandlung der Vereinigung realisierbar. Da eine wiederhergestelltes XML–Fragment aus Knoten besteht, die in einer Variablen gespeichert sind oder durch einen Location Path ausgewählt werden, besteht die Grundmenge von Knoten, aus denen XML–Elemente wiederhergestellt werden, auf jene Knoten, die in einer (temporären) Ergebnisrelation oder durch eine direkt erzeugte Ergebnisrelation enthalten sind. Der Übersichtlichkeit halber sei angenommen, diese bezogenen Grundmengen von zur Verfügung stehenden Knoten ist durch eine Relation result gegeben. SELECT * FROM SELECT ’<’ node.local_name ’>’ AS local_name, node.rgt AS coord, ’’ AS content FROM (SELECT * FROM node WHERE node.node_type = ’element’ AND node.lft IN (SELECT id FROM result)) UNION SELECT ’’ AS local_name, node.lft AS coord 60 4 Umsetzung FROM (SELECT * FROM node WHERE node.node_type = ’text’ AND node.lft IN (SELECT id FROM result)) UNION SELECT ’</’ node.local_name ’>’ AS local_name, node.rgt AS coord, ’’ AS content FROM (SELECT * FROM node WHERE node.node_type = ’element’ AND node.lft IN (SELECT id FROM result)) ORDER BY coord Eine Iteration über die Tupel der erzeugten Relation rekonstruiert die XML–Elemente und Textelemente in Dokumentenreihenfolge. Eine Wiederherstellung von Attributknoten kann durch zusätzliche Auswahl der Attribute– Relation erreicht werden. Um die Darstellung von Attributen in Tags syntaktisch korrekt durch SQL–Anfragen darzustellen, so wie es hier mit Struktur– und Textelementen geschehen ist, ist allerdings eine geschickte Neuindizierung der ausgewählten Knoten und ein hoher Aufwand bei der Erzeugung der Wiederherstellungsrelation nötig, so dass die Darstellung von wiederhergestellten XML–Elementen inklusive zugehörige wiederhergestellter Attribute aufgrund der besseren Manipulationsmöglichkeiten anwendungsseitig umgesetzt wird. 4.6.5 Umsetzung des FLWR–Ausdrucks Die in den vorhergehenden Grundlagen zur Umsetzung der einzelnen Komponenten von FLWR–Ausdrücken sollen nun so zusammengefügt werden dass die Auswertung eines FLWR–Ausdrucks ermöglicht wird. Vorausgesetzt wird hierbei, dass ein FLWR–Ausdruck bereits durch eine XQuery– Parser in einen Syntaxbaum zerlegt und zur Verfügung gestellt wurde. Ausgangspunkt ist die For–Klausel, die einer Variablen in jedem Schleifendurchlauf einen Knoten einer durch einen Location Path bestimmten Knotenmenge zuweist. Die durch diesen Location Path bestimmte Knotenmenge wird durch die Ausführung der generierten SQL–Anweisung entsprechend der o.a. Verfahrensweise als Result Set zur Verfügung gestellt. Über dieses ResultSet wird durch die implementierte Anwendung iteriert. In jedem Iterationsschritt stehen ausschließlich die innerhalb dieses Schritts definierten Variablen bzw. die ihnen zugewiesen Knotenmengen zur Verfügung. Die result – Relation muss dementsprechend zu jedem neuen Iterationsschritt geleert werden, was durch die SQL–Anweisung DELETE * FROM result geschieht. Die Laufvariable der For–Schleife enthält genau einen Knoten, der durch ein Tupel des ResultSet beschrieben wird. Die Knoten–ID sowie der Variablenname wird in die Ergebnisrelation result eingefügt. 61 4 Umsetzung Variablen sollen nicht mehrfach definiert werden können, aus diesem Grund werden Variablendefinitionen anwendungsseitig überwacht. Hierzu ist es ausreichend, in jedem Iterationsschritt der For–Schleife eine neue Liste anzulegen, in die durch For– oder Let– Klauseln definierte Variablennamen eingefügt werden. Vor jeder neuen Variablendefinition muss geprüft werden, ob die zu definierende Variable nicht bereits in dieser Liste vorhanden ist. Ist die Variable noch nicht definiert worden, wird ihr Name in die Liste eingefügt. Da auch die Laufvariable nicht mehrfach definiert werden darf, ist diese die erste, die in diese Liste eingefügt wird. Nachdem sichergestellt wurde, dass eine Variable nicht bereits definiert wurde, werden Variablendefinitionen zunächst danach unterschieden, ob diesen Variablen Literale oder Knotenmengen zugewiesen werden. Eine Zuweisung eines Literals (numerischer Wert oder Zeichenkette) kann nicht sinnvoll in die Ergebnisrelation eingefügt werden, da diese für die Speicherung von Knoten angelegt wurde. Aus diesem Grund werden solche Variablenzuweisungen anwendungsseitig behandelt. Hierzu wird eine Tabelle angelegt, in die ein Variablenname und der ihm zugewiesene Wert eingetragen wird. Diese Tabelle muss wie die Liste der definierten Variablen in jedem neuen Iterationsschritt geleert bzw. neu angelegt werden. Für eine Zuweisung einer Knotenmenge durch einen Location Path wird zunächst eine SQL–Anweisung nach den o. a. Verfahren generiert. Deren Ausführung liefert ein ResultSet. Über diese ResultSet kann iteriert und jede auftretende Knoten–ID zusammen mit dem Variablennamen in die result – Relation eingefügt werden. Effizienter kann dies durch eine SELECT–INTO–Anweisung erfolgen (angenommen sei hier, dass eine Sequenz seq für die Vergabe eindeutiger ID’s verwendet wird; die generierte SQL– Anweisung sei hier als "tmp" bezeichnet). SELECT seq.nextval, $var, lft INTO result FROM tmp Location Paths, die sich auf bereits zuvor definierte Variablen als Ausgangspunkt beziehen, werden wie andere Location Paths auch durch die o. a. Verfahrensweisen zur Generierung von SQL–Anfragen und deren Ausführung bestimmt. Der Unterschied ist hier, dass die Ausgangsmenge nicht durch einen Wurzelknoten oder alle Knoten eines XML–Dokuments bestimmt wird, sondern durch die Knotenmenge, die der Variablen zugewiesen wurden. Aus diesem Grund wird die Grundmenge durch die folgende SQL– Abfrage bestimmt. SELECT node.* FROM (SELECT * FROM node, result WHERE result.varname = $varname AND node.lft = result.id) Diese Abfrage bestimmt alle Knoten der der Variable zugewiesenen Knoten als Tupel der Relation Node. Ein hier ansetzender Location Path schachtelt dann weitere SQL– Statements entsprechend der o. a. Verfahren um dieses Statement herum. Die Ausführung der generierten SQL–Abfrage liefert ein ResultSet, dessen Knoten–IDs wiederum 62 4 Umsetzung zusammen mit dem Variablennamen in die result – Relation eingefügt werden. Zuvor wird sichergestellt, dass die Variable bereits definiert worden ist. Die Return–Klausel gibt für jeden Iterationsschritt eine Zeichenkette zurück. Der Rückgabewert des gesamten FLWR–Ausdrucks ist die Konkatenation dieser einzelnen Zeichenketten. Zu dieser Umsetzung muss ein geeigneter Datentyp (String, StringBuffer) gewählt werden. Dieser muss für den FLWR–Ausdruck global sichtbar sein und darf nicht wie die result – Relation oder die Liste definierter Variablen in jedem Iterationsschritt neu angelegt werden. Werden in den Rückgabewert von Return–Klauseln durch Location Paths oder Variablenreferenzen Knotenmengen eingebaut, müssen diese zuvor serialisiert werden. Eine Vorgehensweise für eine datenbankgestützte Serialisierung von XML–Teildokumenten wurde bereits im Abschnitt über die Umsetzung der Return–Klausel beschrieben. Die Ausführung der dort beschriebenen SELECT–Abfrage liefert ein ResultSet, in dem bereits die Strukturelemente und ihre Textinhalte in korrekter Syntax und Reihenfolge vorhanden sind. Für die Serialisierung genügt also eine Iteration über dieses ResultSet, wobei die Attribute tag und content jeweils nacheinander ausgelesen und an die Return–Zeichenkette angehängt werden. Tupel dieser Relation verfügen über ein befülltes tag – Attribut und leeres content – Attribut oder umgekehrt. Variablen, denen Literale zugewiesen wurden, können in Return–Klauseln direkt eingebaut werden. Die Entscheidung darüber, ob Knotenmengen zu serialisieren sind oder lediglich Literale referenziert werden, kann durch die erstellte Variablentabelle leicht getroffen werden. 63 5 Implementation Im Folgenden wird die Implementierung anhand der Klassen- und Packagestrukturen erläutert. Ein kurzer Überblick schildert dabei die jeweilige Aufgabe eines Package. UML – Klassendiagramme geben einen kurzen Überblick über die Eigenschaften und Methoden der jeweiligen Klasse, deren Aufgabe jeweils kurz vorgestellt wird. Die Klassendiagramme sind dabei auf wesentliche Methoden und Eigenschaften beschränkt worden. Übernehmen Klassen besondere laufzeitrelevante Aufgaben, implementieren sie besondere Algorithmen oder setzen sie besonders relevante Aufgaben um, werden diese ggf. näher erläutert. 5.1 Das Package xml.database Das Package xml.database stellt im wesentliche zwei Utility–Klassen bereit, die den Zugriff auf Datenbanken erleichtern und die entwickelte Datenbankstruktur erzeugen, sofern diese noch nicht besteht. Die Portierung der Implementierung auf andere RDBMS als das hier verwendete Oracle kann durch den Austausch dieses Packages ermöglicht werden. 5.1.1 Die Klasse DatabaseAccess xml.database:: DatabaseAccess configuration: Map resultSet: ResultSet +configure(): Map +executeQuery(): String +executeUpdate(): String +connect() +disconnect() Ein Objekt dieser Klasse wird mit den Parametern für die Verbindung zu einem RDBMS konfiguriert und kann basierend auf diesen Konfigurationsdaten eine Verbindung aufbauen oder auch wieder abbauen. Über die Methoden executeUpdate(String) bzw. executeQuery(String) werden Update–Anweisungen und Select–Anfragen durchgeführt. Die Methode executeQuery(String) liefert das entsprechende ResultSet der Anfrage zurück. 64 5 Implementation 5.1.2 Die Klasse DatabaseStructure xml.database:: DatabaseStructure access: DatabaseAccess +checkStructure() +generateStructure() Die Aufgabe dieser Klasse ist es, zu Beginn jeder Arbeit zu prüfen, ob überhaupt die Datenbankstruktur bereits einmal angelegt worden ist oder nicht. Dies geschieht in diesen auf Oracle durch eine geeignete Abfrage der Relation user_tables. Sind die Datenbankstrukturen noch nicht angelegt worden, werden sie durch diese Klasse angelegt. Eine Sammlung von Konstanten (die in diesem UML–Diagramm aus Platzgründen unerwähnt blieben) definieren jeden Attribut- und Relationennamen und die eingesetzten Datentypen, so dass eine einfache Konfiguration der Datenbankstruktur ermöglicht wird, sollte dies nötig werden. 5.2 Das Package xml.dbstore Das Package xml.dbstore stellt eine Sammlung von Klassen bereit, die zur Aufgabe haben, XML–Dokumente entsprechend den entwickelten Methoden in einer Datenbank zu speichern. Die Klassen dieses Packages stellen unter anderem Methoden bereit, um die entwickelte Datenbankstruktur zu generieren, sofern diese noch nicht vorhanden ist. Diese Klassen übernehmen auch die Nummerierung der XML–Knoten. 5.2.1 Die Klasse NestedSetModelNode xml.dbstore::NestedSetModelNode lft: int rgt: int node: Node parent: long Diese Klasse stellt eine Datenstruktur dar, die geeignet ist, Informationen des Nested Set Model aufzunehmen. Ein Objekt dieser Klasse verfügt über zwei Koordinaten und den dazugehörigen XML–Knoten. Diese Klasse kommt bei der Nummerierung der Knoten eines DOM–Baums zum Einsatz. Zu diesem Zweck verfügt ein Objekt dieser Klasse zusätzlich über die Eigenschaft parent, die mit der Knoten-ID des Elternknoten entsprechend dem Nummerierungsschema versehen wird. 65 5 Implementation 5.2.2 Die Klasse StorableDocument xml.dbstore::StorableDocument nodeStack: Stack coordinate: int document: Document databaseAccess: DatabaseAccess +storeNodes() +storeNestedSetNode() +readFromFile() +readFromDatabase() : long +writeToDatabase() +writeToFile() Diese Klasse hat zur Aufgabe, XML–Dokumente nach den entwickelten Methoden in der Datenbank zu speichern. Zu diesem Zweck übernimmt diese Klasse die Nummerierung von DOM–Knoten unter Verwendung der Klasse NestedSetModelNode. Ein Objekt dieser Klasse verfügt über ein XML–Dokument in Form eines DOM–Baums sowie eine Zugriffsmöglichkeit auf ein RDBMS, die durch ein Objekt der Klasse DatabaseAccess bereitgestellt wird. Für die Nummerierung implementiert diese Klasse eine iterative Version des im dritten Kapitel vorgestellten Algorithmus des Eulerschen Umlaufs um einen Baum, dessen Implementierung kurz vorgestellt wird. private void storeNodes(Node rootNode, long startId) throws SQLException { // Initialisierung Stack nodeStack = new Stack(); int coord = 1; long id = startId; NestedSet startNode = new NestedSet(rootNode); startNode.setLeftId(coord); nodeStack.push(startNode); while (!nodeStack.empty()) { NestedSet node = (NestedSet) nodeStack.peek(); // wurde der Knoten bereits indiziert? if (node.getLeftId() == 0) { coord++; id++; node.setLeftId(coord); } 66 5 Implementation // wurden alle Nachfolgerknoten abgearbeitet? if (node.isChildrenSeen()) { coord++; nodeStack.pop(); node.setRightId(coord); storeNestedSet(node); } else { nodeStack.pop(); node.setChildrenSeen(); if (node.getLeftId() < 1) { coord++; node.setLeftId(coord); } nodeStack.push(node); // lege alle Nachfolgerknoten in umgekehrter // Reihenfolge auf den Stack for (int i = node.getNode().getChildNodes().getLength() - 1; i >= 0; i--) { NestedSet nodeToStack = new NestedSet( node.getNode().getChildNodes().item(i), node.getId()); nodeStack.push(nodeToStack); } } } Die Idee bei der Umwandlung der Rekursion des in Kapitel 3 vorgestellten Algorithmus in eine Iteration ist hier, den rekursiven Aufruf der Funktion selber zu unterbinden und notwendige Zwischenergebnisse in den Arbeitsspeicher auszulagern. Diese Auslagerung geschieht über die Verwendung eines Stack. Die Abarbeitungreihenfolge, die durch den Eulerschen Umlauf vorgeschrieben wird, wird beibehalten. Um dies zu erreichen, wird ein Knoten von links indiziert und auf den Stack gelegt. Danach werden die direkten Nachfolgerknoten in umgekehrter Reihenfolge auf dem Stack abgelegt, so dass der erster Kinderknoten das Top–Element des Stacks bildet. Danach wird der Stack von oben abgearbeitet. Das Top–Element wird von links indiziert. Hat es Kinderknoten, werden sie in umgekehrter Reihenfolge auf den Stack gelegt. Ein Flag isChildrenSeen legt dabei fest, ob von einem Knoten bereits die Liste seiner Kinderknoten abgearbeitet wurden. Ist ein Top–Element abgearbeitet, sind also seine Kinderknoten indiziert worden, wird es von rechts indiziert und vom Stack entfernt. Ist der Stack leer, ist der DOM–Baum abgearbeitet, denn dann wurden alle Nachfahren des Wurzelknoten sowie er selbst abgearbeitet, sprich indiziert. Das Speichern eines Knoten übernimmt die Methode storeNestedSetNode(). Diese 67 5 Implementation Methode bedient sich dabei Objekten der Klasse NestedSetModelNode, die durch den Nummerierungsalgorithmus mit Koordinaten versehen wurden. Anhand der enthalten DOM–Objekten entscheidet diese Methode darüber, um welchen Knotentyp es sich handelt und welche Eigenschaften des Knoten gespeichert werden. 5.3 Das Package xml.xquery Das Package xml.xquery stellt eine Sammlung von Klassen bereit, die zur Aufgabe haben, XQuery–Anfragen, speziell FLWR–Ausdrücke auszuwerten. Grundlage dieser Klassen ist ein Syntaxbaum einer geparsten XQuery–Anfrage, der durch einen mit Hilfe von ANTLR ([Par]) (ein im Rahmen dieser Arbeit eingesetzter Parsergenerator) erzeugten XQuery–Parser generiert wird. 5.3.1 Die Klassen XQueryLexer und XQueryParser xml.xquery:: XQueryLexer xml. xquery:: XQueryParser access: DatabaseAccess +nextToken() tokenSet: Set +flwr_expr() +for_expr() +let_expr() +where_expr() +return_expr() +… Diese beiden Klassen stellen eine XQuery–Parser bereit, mit dessen Hilfe ein als Zeichenkette übergebener Ausdruck eingelesen und geparst wird. Hierzu wird ein Syntaxbaum generiert. Diese Klassen wurden nicht selbst implementiert, sondern durch den Parsergenerator ANTLR generiert. ANTLR generiert Quellcode für Parser basierend auf einer Konfigurationsdatei. Im Wesentlichen können in dieser Konfigurationsdatei Literale, Tokens und eine kontextfreie Grammatik in EBNF angegeben werden. Zusätzlich besteht die Möglichkeit, innerhalb von Produktionen der Grammatik beliebigen Quellcode mit einzufügen, der dem generierten Parserquellcode hinzugefügt und zur Laufzeit des Parsens ausgeführt wird. Die Vorteile des Einsatzes von ANTLR als Parsergenerator sind vielschichtig. Bei ANTLR handelt es sich um einen kostenlosen und frei verfügbaren Parsergenerator, der sowohl für kommerzielle als auch private Zwecke eingesetzt werden darf. ANTLR ist komplett in Java implementiert und liefert reinen Java–Quellcode, was hier insofern von Vorteil ist, als dass auch die übrigen Implementierungen in Java geschrieben sind. Die Möglichkeit, Produktionen mit zusätzlichem beliebigen Java–Quellcode zu versehen, der in den generierten Parser übernommen wird, kann diesen mit geringem Aufwand zu einem äußerst mächtigen Werkzeug aufbauen (wobei von dieser Möglichkeit, wie bereits 68 5 Implementation im vierten Kapitel erwähnt wurde, lediglich zur Erzeugung des Syntaxbaums Gebrauch gemacht wurde). ANTLR ist zudem ein LL(k)–Parsergenerator. LL(k) ist eine Bezeichnung für die Art und Weise, wie Ausdrücke zu parsen und Grammatiken abzuleiten sind. Das erste "L" steht für das Einlesen eines Ausdrucks von links nach rechts. Das zweite "L" steht für Linksableitung. "k" steht für eine theoretisch beliebige Lookahead–Tiefe des Lexers. Außerdem basiert die Generierung von Parsern bei ANTLR auf einer Grammatik, die in EBNF definiert wird. Da die komplette XQuery–Grammatik ohnehin in EBNF spezifiziert ist, werden Konfigurationen des zu erzeugenden Parsers vereinfacht. Im Folgenden soll ein sehr kurzer Einblick in eine Produktionen und einige Tokensowie Literaldefinitionen der Konfigurationsdatei für ANTLR gegeben werden. Die komplette Konfiguration besteht aus über 1200 Zeilen. SEMI COLON DIGIT :’;’; :’:’; :’0’..’9’; tokens { DESCENDANT = "descendant"; CHILD = "child"; FOLLOWING = "following"; } forward_axis returns [java.util.Map tmp=new java.util.HashMap()]: (DESCENDANT COLON COLON {tmp.put("forward_axis","descendant");}) | (CHILD COLON COLON {tmp.put("forward_axis","child");}) | (ATTRIBUTE_AXIS {tmp.put("forward_axis","attribute");}) | (SELF COLON COLON {tmp.put("forward_axis","self");}) | (DESCENDANT_OR_SELF COLON COLON {tmp.put("forward_axis","descendant-or-self");}) | (FOLLOWING_SIBLING COLON COLON {tmp.put("forward_axis","following-sibling");}) | (FOLLOWING COLON COLON {tmp.put("forward_axis","following");}) | (NAMESPACE COLON COLON {tmp.put("forward_axis","namespace");}); Tokens und Literaldefinitionen sind selbsterklärend. Die Produktionen können mit return–Anweisungen versehen werden. Beliebiger Javaquellcode kann in geschweiften 69 5 Implementation Klammern angegeben werden. ANTLR erzeugt zu diesen Produktionen Quellcode, so wie nachfolgend anhand des Beispiels der Produktion forward_step zu sehen ist. Jede Produktion erhält dabei eine eigene Methode, die nach dem Namen der Produktion benannt wird. public final java.util.Map forward_step() throws RecognitionException, TokenStreamException { java.util.Map tmp=new java.util.HashMap(); java.util.Map v1=new java.util.HashMap(); java.util.Map v2=new java.util.HashMap(); try { // for error handling { switch ( LA(1)) { case NAMESPACE: case FOLLOWING: case FOLLOWING_SIBLING: case DESCENDANT_OR_SELF: case SELF: case CHILD: case DESCENDANT: case ATTRIBUTE_AXIS: { { v1=forward_axis(); v2=node_test(); if ( inputState.guessing==0 ) { tmp.put("forward_axis",v1);tmp.put("node_test", v2); } } break; } case SCHEMA_ATTRIBUTE: case SCHEMA_ELEMENT: case ELEMENT: case ATTRIBUTE: case PROCESSING_INSTRUCTION: case COMMENT: case TEXT: case DOCUMENT_NODE: case NODE: case STAR: case AT: case NAME: { 70 5 Implementation { v1=abbrev_forward_step(); if ( inputState.guessing==0 ) { tmp.put("abbrev_forward_step",v1); } } break; } default: { throw new NoViableAltException(LT(1), getFilename()); } } } } catch (RecognitionException ex) { if (inputState.guessing==0) { reportError(ex); consume(); consumeUntil(_tokenSet_3); } else { throw ex; } } return tmp; } Anhand der folgenden Produktion soll verdeutlicht werden, wie der Syntaxbaum erstellt wird. forward_step returns [java.util.Map tmp=new java.util.HashMap()] {java.util.Map v1=new java.util.HashMap(); java.util.Map v2=new java.util.HashMap();}: ( (v1=forward_axis v2=node_test {tmp.put("forward_axis",v1);tmp.put("node_test", v2);}) | (v1=abbrev_forward_step {tmp.put("abbrev_forward_step",v1);}) ); Alle Produktionen wurden mit einem Rückgabewert java.util.HashMap() versehen, so auch die Rückgabewerte der Produktionen forward_axis und node_test bzw. abbrev_forward_step. In der Produktion forward_step wird eine neue HashMap angelegt. Je nachdem, welche der beiden Produktionen abgeleitet werden können, werden 71 5 Implementation in dieser HashMap Schlüssel–Wert–Paare abgelegt, deren Schlüsselwerte die Namen der ableitbaren Produktionen und deren Werte die Rückgabewerte dieser Produktionen bilden. Durch den Umstand, dass auch die abgeleiteten Produktionen so verfahren, ergibt sich dadurch eine Baumstruktur, deren Blätter aus Terminalsymbolen (Tokens und Literale) bestehen, da diese keine neuen Produktionen mehr aufrufen. 5.3.2 Die Klasse FlwrExpression xml.xquery:: FlwrExpression flwrExpression: String syntaxTree: HashMap result: String boundVariables: HashMap databaseAccess: DatabaseAccess resultSet: ResultSet +eval(): String +bindVariable(): String, ResultSet +generateSqlFromPath(): Map, String, int Diese Klasse bildet die Schnittstelle zur Auswertung von FLWR–Ausdrücken. Der Methode String eval(String) wird dabei ein zu parsender Ausdruck übergeben. Als Rückgabewert liefert diese Methode die Auswertung des übergebenen Ausdrucks. Diese Klasse ist als eine Art Controller für die Auswertung der Ausdrücke konzipiert. Sie übergibt dem generierten Parser den auszuwertenden Ausdruck und erhält einen Syntaxbaum zurück. Aus diesem werden, sofern der Parser den Ausdruck erfolgreich als FLWR–Ausdruck parsen konnte, die Bestandteile der einzelnen Komponenten (For– Klausel, Let–Klausel etc.) extrahiert und in handhabbarere Formate zerlegt. Ein Location Path wird beispielsweise in eine Liste von Axis Steps zerlegt. Ein Axis Step wird in seine Achse und Typeinschränkung zerlegt und diese in einer Map als Schlüssel–Wert–Paare abgelegt. Diese Map wird der Location–Path–Liste hinzugefügt. Nach welchen Vorgehensweisen SQL–Statements aus Axis Steps generiert werden können, wurde in Kapitel 4 behandelt. Hier soll jetzt der konkrete Vorgang des Schachtelns von SQL–Statements anhand eines kleines Beispiels demonstriert werden. /descendant::element(elem1)/following::element(elem2) Dieser Location Path wird zerlegt in eine Liste, die für jeden Axis Step eine Eintrag erhält. Dieser Eintrag ist eine Map, die die jeweilige Achse und die Typeinschränkung enthält. Über diese Liste wird nun iteriert und jeder Eintrag sowie anfänglich ein SQL– Statement als Zeichenkette und eine mit jedem neuen Axis Step inkrementierten temporären Zähler der Methode String generateSqlFromStep(Map, String, int) übergeben, die die Ausgangsmenge des Location Path bestimmt. Da hier der Wurzelknoten eines XML–Dokuments die Ausgangsmenge darstellt, lautet diese Statement entsprechend Kapitel 4 wie folgt: 72 5 Implementation SELECT * FROM node WHERE node_type = ’document’ private String generateSqlFromStep(Map map, String query, int depth) { String axis = map.get("axis"); String test = map.get("test"); List params = map.get("params"); String selectFrom = ""; String whereOrder = ""; if(axis.equals("descendant")) { selectFrom = "SELECT node.* FROM ("; whereOrder = ") AS tmp" + depth + ", node WHERE node.lft > tmp1.lft AND node.rgt < tmp1.rgt ORDER BY node.lft"; String tmp = selectFrom + query + whereOrder; return tmp; } [...] } Die zurückgegebene zusammengesetzte Zeichenkette bildet ein geschachteltest SQL– Statement, welches etwa die folgende Gestalt hat. SELECT FROM node.* (SELECT * FROM node WHERE node_type = ’document’) AS tmp1, node WHERE node.lft > tmp1.lft AND node.rgt < tmp1.rgt ORDER BY node.lft Dieses zusammengesetzte Statement wird im nächsten Iterationsschritt wieder der Methode generateSqlFromStep() übergeben, bis die Liste der Axis Steps durchgearbeitet wurde. Danach wird das generierte SQL–Statement durch das DatabaseAccess–Objekt ausgeführt und abhängig davon, ob eine Variablenzuweisung durch eine For–Klausel oder eine Let–Klausel erfolgte, in die Variablenrelation eingefügt oder das sich ergebende ResultSet der entsprechenden Objektvariablen resultSet zugewiesen. Wurde ein ResultSet aufgrund einer For–Klausel zugewiesen, wird über dieses ResultSet iteriert. Die Variablenrelation wird geleert und das jeweils aktuelle Ergebnistupel bzw. die entsprechenden Attribute in die Variablenrelation gespeichert. Am Ende eines solchen Iterationsschritts wird an die anfangs leere Zeichenkette jeweils die Auswertung der Return–Klausel angehängt. Nach Ablauf aller Iterationsschritte wird diese konkatenierte Zeichenkette zurückgegeben. 73 6 Kritik und Ausblick Im Rahmen dieser Arbeit wurden Methoden und Modelle entwickelt, die geeignet sind, XML–Daten generisch in einer relationalen Datenbank zu speichern, so dass die Semantik der XML–Daten nicht verändert wird und die ursprünglichen XML–Dokumente wiederherstellbar bleiben. Dabei wurde die Unterstützung bestimmter XML–Elementtypen wie Verarbeitungsanweisungen und Kommentare in erster Linie aus Gründen der Übersicht und der relativen Bedeutungslosigkeit für XQuery–Anfragen im Vergleich zu Strukturelementen, Textelementen usw. eingeschränkt. Um eine vollständige Wiederherstellbarkeit zu gewährleisten und eher selten auftauchende, aber dennoch denkbare Anfragen an diese vernachlässigten Elementtypen oder ihre Inhalte im vollen Umfang zu unterstützen, sollten diese ebenfalls Beachtung in den entwickelten Methoden und Modellen finden. Die in dieser Arbeit entwickelte Unterstützung von XQuery unterliegt Einschränken. Insbesondere wurden nur Möglichkeiten zur Umsetzung von FLWR–Ausdrücken entwickelt. Diese stellen lediglich eine kleine Teilmenge von XQuery dar, die jedoch häufig Anwendung findet. Weitere interessante Konstrukte dieser Sprache sind if–then–else – Konstrukte, Listenoperationen und Definition eigener Funktionen wurden in diesem Zusammenhang nicht beachtet. Weiterentwicklungen der entwickelten Methoden sollten abwägen, inwieweit sich der unterstützte Sprachumfang von XQuery auf diese Konstrukte erweitern ließe. FLWR–Ausdrücke stellen lediglich eine Teilmenge von FLWOR–Ausdrücken dar, ihnen fehlt die order–by–Klausel, das im Rahmen dieser Entwicklungen außen vor gelassen wurde. Der Grund hierfür ist die gewählte Datenbankstruktur, die in ihrem Aufbau für die Umsetzung dieser Klausel bislang nicht geeignet ist. Weiterentwicklungen dieser Datenbankstruktur sollten versuchen, durch zusätzliche Attribute, Indexstrukturen oder den Einsatz weiterer Nummerierungsschemata Grundlagen zu schaffen, um diese Sortierungen nach Möglichkeit datenbankseitig umsetzen zu können. Neben der ausgereiften und effizienten Umsetzung von Sortieralgorithmen durch RDBMSe bietet eine solche Umsetzung auch den Vorteil, dass XML–Daten in den hier entwickelten Strukturen allenfalls zu Serialisierungszwecken oder als Eingabemengen von Funktionen aus der Datenbank in eine Client–Applikation geladen und verarbeitet werden und ansonsten im RDBMS verbleiben. Lassen sich keine geeigneten Datenbankerweiterungen finden, die in der Lage sind, order–by–Klauseln effizient datenbankseitig umzusetzen, kann überlegt werden, inwiefern sich anwendungsseitig umgesetzte Sortieralgorithmen für XML–Daten so umsetzen lassen, dass sie effizient ausgewertet werden können. FLWOR–Ausdrücke können nach den XQuery–Spezifikationen auch geschachtelt verwendet werden. Insbesondere die Tatsache, dass Return–Klauseln neue XML–Dokumente erzeugen, auf die weitere FLWOR–Ausdrücke angewendet werden können, ist sehr interessant. Um solche Schachtelungen vornehmen zu können, ist man hier bislang darauf 74 6 Kritik und Ausblick angewiesen, ein von einem FLWOR–Ausdruck erzeugtes XML–Teildokument als neues XML–Dokument zu betrachten, das dieselbe Behandlung wie andere, als Textdatei vorliegenden Dokumente auch erfordern, wenn sie durch die hier entwickelten Methoden angefragt werden sollen. Sie müssen als neue XML–Dokumente zuvor in die Datenbank geladen werden. Nur so ist sichergestellt, dass sie über die gleichen Nummerierungsstrukturen wie andere XML–Daten auch verfügen und durch die entwickelten SQL–Anfragen ausgewertet werden können, die sich hauptsächlich auf diese Strukturen stützen. Hinsichtlich der geforderten Effizienz umgesetzter Anfragen wird dieses Vorgehen kaum in Betracht kommen. Es bleibt also die Frage, ob und wie sich solche Schachtelungen von FLWOR–Ausdrücken effizient umsetzen lassen. Ein erster Ansatz hierfür ist sicherlich die Umsetzung eines weiteren XQuery–Konzepts, die sogenannten Elementkonstruktoren. 75 Literaturverzeichnis [AYBB+ 04] Amer-Yahia, Sihem, Chavdar Botev, Stephen Buxton, Pat Case, Jochen Doerre, Darin McBeath, Michael Rys und Jayavel Shanmugasundaram: XQuery 1.0 and XPath 2.0 Full-Text. W3C Working Draft, World Wide Web Consortium (W3C), July 2004. [BBC+ 04] Berglund, Anders, Scott Boag, Don Chamberlin, Mary F. Fernández, Michael Kay, Jonathan Robie und Jérôme Siméon: XML Path Language (XPath) 2.0. W3C Working Draft, World Wide Web Consortium (W3C), October 2004. [BPSM+ 04] Bray, Tim, Jean Paoli, C. M. Sperberg-McQueen, Eve Maler und François Yergeau: Extensible Markup Language (XML) Version 1.0 (Third Edition). W3C Recommendation, World Wide Web Consortium (W3C), February 2004. [CD99] Clark, James und Steve DeRose: XML Path Language (XPath) 1.0. W3C Recommendation, World Wide Web Consortium (W3C), November 1999. [Cod70] Codd, E. F.: Communications of the ACM. Association for Computing Machinery, Inc., 1970. [CT04] Cowan, John und Richard Tobin: XML Information Set (Second Edition). W3C Recommendation, World Wide Web Consortium (W3C), February 2004. [DFF+ 98] Deutsch, Alan, Mary Fernandez, Daniela Florescu, Alon Levy und Dan Sucui: XML-QL: A Query Language for XML. Submission to the World Wide Web Consortium, August 1998. [Fer03] Ferber, Reginald: Information Retrieval. dpunkt, 2003. [fSI86] Standardization (ISO), International Organization for: Standard Generalized Markup Language (SGML), 1986. [fSI03] Standardization (ISO), International Organization for: Database Languages - SQL - Part 14: XML-related Specifications (SQL/XML), 2003. 76 Literaturverzeichnis [GGR+ ] Garofalakis, Minos, Aristides Gionis, Rajeev Rastogi, S. Seshadri und Kyuseok Shim: XTRACT - A System for Extracting Document Type Descriptors from XML Documents. [GML60] Goldfarb, Charles, Edward Mosher und Raymond Lorie: Generalized Markup Language (GML). IBM, 1960. [HHW+ 04] Hors, Arnaud Le, Philippe Le Hégaret, Lauren Wood, Gavin Nicol, Jonathan Robie, Mike Champion und Steve Byrne: Document Object Model (DOM) Level 3 Core Specification Version 1.0. W3C Recommendation, World Wide Web Consortium (W3C), April 2004. [Kur04] Kuropka, Dominik: Modelle zur Repräsentation natürlichsprachlicher Dokumente - Ontologie-basiertes Information-Filtering und -Retrieval mit relationalen Datenbanken. Logos Berlin, 2004. [MLN] Moh, Chuang-Hue, Ee-Peng Lim und Wee-Keong Ng: DTD-Miner : A Tool for Mining DTD from XML Documents. [Par] Parr, Terence: ANTLR ANother Tool for Language Recognition, v 2.7.4. [RDF+ 99] Robie, Jonathan, Eduard Derksen, Peter Fankhauser, Ed Howland, Gerald Huck, Ingo Macherius, Makato Murata, Michael Resnick und Harald Schöning: XQL (XML Query Language), Augsut 1999. 77