Realisierung einer temporalen Erweiterung von SQL auf einem objekt-relationalen Datenbankmanagementsystem Diplomarbeit im Studiengang Mathematik mit der Studienrichtung Informatik an der Universität Hannover vorgelegt von Carsten Reinhard Betreut von Prof. Dr. Udo Lipeck Universität Hannover, Institut für Informatik Fachgebiet Datenbanken und Informationssysteme Zweitgutachter Prof. Dr. techn. Wolfgang Nejdl Universität Hannover Institut für Rechnergestützte Wissensverarbeitung Hannover, 22. Dezember 1999 ii Zusammenfassung Die Zeit ist eine allseits vorhandene Komponente der realen Welt, und alle denkbaren Informationen unterliegen letztlich einer zeitlichen Veränderung. Es wundert daher nicht, daß in fast allen vorhandenen Datenbanksystemen zeitbehaftete Daten verarbeitet werden. Dennoch ist die gegenwärtige Unterstützung temporaler Aspekte in den verbreiteten Datenbanksprachen relativ gering. Es hat daher in der Vergangenheit verschiedene Ansätze gegeben, neue Datenbanksprachen mit temporaler Funktionalität zu entwickeln. Auch für zukünftige Standards — etwa SQL3 — ist man darum bemüht, eine inhärente temporale Unterstützung zu vereinbaren. In der vorliegenden Arbeit werden daher zunächst grundsätzliche Prinzipien zur temporalen Erweiterung von Datenbanksprachen erläutert. Diese sollen eine umfangreiche temporale Unterstützung bei gleichzeitiger Aufwärtskompatibilität und leichter Erlernbarkeit der neuen Sprache sicherstellen. Weiterhin wird, anhand von Beispielen und durch die Beschreibung des zugrundeliegenden temporalen Datenmodells, mit ATSQL2 eine prototypisch verfügbare temporale Erweiterung von SQL vorgestellt, die auf Basis eines relationalen Datenbanksystems implementiert wurde. Auf dieser Anfragesprache aufbauend wird eine neue temporale Erweiterung von SQL formuliert. Das Ziel ist dabei, in höherem Maße temporale Semantik zur Verfügung zu stellen als das in der genannten Implementierung wegen der Verwendung eines rein relationalen Datenbanksystems der Fall gewesen ist. Mit Hilfe der erst in jüngster Zeit kommerziell verfügbaren objekt-relationalen Möglichkeiten ist trotz der umfangreicheren temporalen Funktionalität eine relativ einfache Übersetzung der temporalen Anfragesprache erreichbar. Auf dieser Grundlage wird schließlich die neue temporale Erweiterung von SQL unter Verwendung eines vorhandenen objekt-relationalen Datenbankmanagementsystems realisiert und als Prototyp implementiert. iii iv Inhaltsverzeichnis 1 Einleitung 1.1 Problemstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.2 Gliederung der Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Grundbegriffe 2.1 Temporale Bezeichnungen . . . . . . . . . . 2.1.1 Dimensionen der Zeit . . . . . . . . 2.1.2 Darstellung der Zeit . . . . . . . . . 2.2 Zeitstempel . . . . . . . . . . . . . . . . . . 2.2.1 Inhalt der Zeitstempel . . . . . . . . 2.2.2 Anwendung der Zeitstempel . . . . . 2.2.3 Temporale Beziehungen . . . . . . . 2.3 Temporale Erweiterung von Datenmodellen 2.3.1 Aufwärtskompatibilität . . . . . . . 2.3.2 Schnappschuß-Reduzierbarkeit . . . 2.3.3 Temporale Vollständigkeit . . . . . . 1 1 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 4 4 5 6 7 9 10 10 12 15 17 3 Temporale Erweiterungen von SQL 3.1 Der Sprachvorschlag ATSQL2 . . . . . . . . . . . . 3.1.1 Syntax . . . . . . . . . . . . . . . . . . . . . 3.1.2 Semantik . . . . . . . . . . . . . . . . . . . 3.2 Die neue temporale Erweiterung SQLTE . . . . . . 3.2.1 Grundlegende Konzepte . . . . . . . . . . . 3.2.2 Temporale Anfrageformulierung . . . . . . . 3.2.3 Temporale Datenmanipulation . . . . . . . 3.2.4 Temporale Datendefinition . . . . . . . . . 3.2.5 Präsentation temporaler Anfrageergebnisse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 19 20 24 27 28 30 34 37 38 . . . . . . . . 40 40 41 44 45 46 47 48 53 4 Planung und Entwurf der Implementation 4.1 Objekt-relationale Möglichkeiten . . . . . . 4.1.1 Kollektionen . . . . . . . . . . . . . 4.1.2 Referenzen . . . . . . . . . . . . . . 4.1.3 Methoden . . . . . . . . . . . . . . . 4.1.4 Oracle 8.1 vs. Oracle 8.0 . . . . . . . 4.2 Varianten des Speichermodells . . . . . . . . 4.2.1 Einfache Schachtelung . . . . . . . . 4.2.2 Doppelte Schachtelung . . . . . . . . v . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vi INHALTSVERZEICHNIS 4.3 4.2.3 Festlegung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Schichtenarchitektur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Implementation 5.1 Programmübersicht . . . . . . . . . . . . . . . 5.2 Datenbankstrukturen . . . . . . . . . . . . . . 5.2.1 Grundlegende Vereinbarungen . . . . . 5.2.2 Objekte . . . . . . . . . . . . . . . . . 5.2.3 Funktionen . . . . . . . . . . . . . . . 5.2.4 Temporäre Tabellen . . . . . . . . . . 5.3 Programmbeschreibung . . . . . . . . . . . . 5.3.1 Globale Strukturen . . . . . . . . . . . 5.3.2 Ein- und Ausgabeverarbeitung . . . . 5.3.3 Hauptprogramm . . . . . . . . . . . . 5.3.4 Lexikalische Analyse . . . . . . . . . . 5.3.5 Syntaktische Analyse . . . . . . . . . . 5.3.6 Semantische Analyse und Übersetzung 5.3.7 Datenbankschnittstelle . . . . . . . . . 5.4 Testdatenbank . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 60 63 63 64 65 65 66 66 67 68 69 71 71 72 74 81 85 6 Ausblick 87 A Syntax der temporalen Erweiterung 89 B Dokumentation der Programmdateien B.1 Datenbankstrukturen . . . . . . . . . . . . . . . . . . . . . . . B.1.1 Hauptskript — sqlte.sql . . . . . . . . . . . . . . . . . B.1.2 Datentyp Intervall — interval.sql . . . . . . . . . . . . B.1.3 Datentyp Zeitstempel — stamp.sql . . . . . . . . . . . B.1.4 Paketfunktionen — package.sql . . . . . . . . . . . . . B.1.5 Deinstallationsskript — dropsqlte.sql . . . . . . . . . . B.1.6 Test des komplexen Speichermodells — kommode.zip . B.2 Übersetzungsprogramm . . . . . . . . . . . . . . . . . . . . . B.2.1 Globale Strukturen — sqlte.h . . . . . . . . . . . . . . B.2.2 Hauptprogramm — sqlte.pc . . . . . . . . . . . . . . . B.2.3 Eingabeverarbeitung — eingabe.pc . . . . . . . . . . . B.2.4 Ausgabeverarbeitung — ausgabe.pc . . . . . . . . . . B.2.5 Lexikalische Analyse — scanner.pc . . . . . . . . . . . B.2.6 Syntaktische Analyse — parser.pc . . . . . . . . . . . B.2.7 Semantische Analyse und Übersetzung — codegen.pc B.2.8 Datenbankschnittstelle — oracle.pc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 91 91 92 92 93 94 95 95 95 95 96 97 98 100 103 106 Literaturverzeichnis 110 Abbildungsverzeichnis 113 Kapitel 1 Einleitung 1.1 Problemstellung Der Titel legt es bereits nahe: Die vorliegende Arbeit beinhaltet den Entwurf und die Implementierung einer temporalen Erweiterung der Datenbanksprache SQL mit Hilfe eines objektrelationalen Datenbankmanagementsystems. Nachfolgend soll daher zunächst ein grober Überblick über die Begriffe der temporalen Erweiterung und des objekt-relationalen Datenbanksystems gegeben werden, eine präzisere Beschreibung der beteiligten Datenbanksprachen erfolgen, sowie die Notwendigkeit der Zuhilfenahme eines vorhandenen DBMS erläutert werden. Allen Datenbanksystemen und Anfragesprachen gemein ist die Möglichkeit, Informationen über Zeit in irgendeiner Form abzulegen und zu verarbeiten. Üblicherweise geschieht dieses mit Hilfe spezieller Datentypen, die es ermöglichen, z. B. Datums- oder Zeitangaben abzulegen. Eine temporale Erweiterung einer konventionellen (nicht-temporalen) Datenbanksprache verlangt jedoch mehr, nämlich eine in der Sprache eingebaute Unterstützung zur Auswertung zeitabhängiger Informationen. Eine temporale Anfragesprache soll es dem Benutzer mit Interesse an zeitveränderlichen Daten ermöglichen, sich nicht mit der expliziten Auswertung einzelner temporaler Attribute beschäftigen zu müssen. Wie aufwendig diese explizite Formulierung im Vergleich zu der Benutzung einer leistungsfähigen temporalen Datenbanksprache ist, wird in [ZCF97, ch. 5] anhand einer Fallstudie erläutert. Ebenso wird dort beschrieben, daß fast alle Datenbankapplikationen von zeitveränderlichen Daten betroffen seien und somit die Notwendigkeit einer solchen datenbankseitigen temporalen Unterstützung belegt. Präzise handelt es sich bei der hier zu erweiternden Datenbanksprache um den Sprachstandard SQL-92 [SQL92, MS93]. Da die Festlegung eines nachfolgenden Standards in Form von SQL3 und insbesondere der temporalen Erweiterung in Form von SQL/Temporal [SQL99] als Teil von SQL3 noch auf sich warten läßt, ist der Sprachvorschlag TSQL2 [SAA94a, SAA94b, ZCF97, Myr97] bislang der wohl breiteste Konsens für eine temporale Erweiterung von SQL92. Darüber hinaus wurde TSQL2 auf Basis der weiterentwickelten Version Applied TSQL2 [BJS95] — im Gegensatz zu vielen anderen Sprachvorschlägen — bereits in verschiedenen Varianten implementiert. Als aktuell verfügbare Implementationen sind vor allem Tiger [BJ96, Böh97] und TimeDB 2.0 [Ste98a, Ste98b] zu nennen. Eine Übersicht über vorhandene Prototypen auch anderer Anfragesprachen gibt [Böh95]. Weiterhin wurden auf der Grundlage von ATSQL2 Änderungsvorschläge für SQL/Temporal unterbreitet [SBJ96a, SBJ96b, SBJ97], so daß der sich entwickelnde SQL3-Standard vermut- 1 2 KAPITEL 1. EINLEITUNG lich entsprechende Übereinstimmungen aufweisen wird. Aus den genannten drei Gründen wird sich die Spracherweiterung der vorliegenden Arbeit eng an den Veröffentlichungen zu ATSQL2 orientieren. Die Implementierung einer temporalen Anfragesprache kann auf verschiedenen Ebenen erfolgen. Die Entwicklung von Datenbankapplikationen mit zeitveränderlichen Daten soll durch die inhärente temporale Unterstützung der Sprache vereinfacht werden. Damit möglichst vielen, auch bereits vorhandenen Applikationen die zusätzliche Funktionalität zur Verfügung steht, wäre es wünschenswert, diese auf einer niedrigen, DBMS-nahen Ebene anzusiedeln. Auch scheint dieses im Sinne einer hohen Performance neuer Operationen sinnvoll. Leider kann in vorhandene DBMS nicht in dem benötigten Umfang eingegriffen werden. Andererseits erscheint es zu zeitaufwendig, ein eigenes, selbst nur rudimentäres, DBMS zu entwickeln. Daher soll hier, wie auch bei den o. g. Implementationen Tiger und TimeDB, auf ein vorhandenes kommerzielles DBMS aufgesetzt werden. Kommandos der temporalen Datenbanksprache werden dabei in SQL übersetzt und in der vorhandenen Datenbank ausgeführt. Auf diese Weise können alle wichtigen Funktionen des zugrundeliegenden DBMS genutzt werden, wenngleich es den Verzicht auf die zuvor beschriebenen Vorteile mit sich bringt. Im Gegensatz zu den beiden genannten vorhandenen Implementationen, die auf der Basis der Funktionalität relationaler DBMS arbeiten, sollen in dieser Arbeit die zusätzlichen Möglichkeiten eines objekt-relationalen DBMS genutzt werden. Diese Systeme basieren nicht mehr auf dem reinen relationalen Datenmodell, sondern sind um Konzepte der Objektorientierung erweitert worden. Dabei sind die umgesetzten Konzepte unterschiedlich stark ausgeprägt. Hier soll das kommerzielle DBMS Oracle in der Version 8 Verwendung finden, das mit der sogenannten objects option u. a. die Möglichkeiten der benutzerdefinierten Objekttypen, Methoden, Referenzen und kollektionswertigen Attributen zur Verfügung stellt. Eine Einführung der in Oracle gebotenen objekt-relationalen Konzepte geben [HP98] und [Ora98a, Part IV]. Die Vorteile der Nutzung objekt-orientierter Konzepte in relationalen Datenbanken ergeben sich direkt aus den Stärken der objekt-orientierten Programmierung: Sie liegen u. a. in der Möglichkeit der natürlichen Modellierung der realen Welt sowie einer leichten Wart- und Erweiterbarkeit der entwickelten Programme. Durch die Nutzung der objekt-relationalen Erweiterungen sollen hier genau diese Vorteile auch bei der Implementierung einer temporalen Anfragesprache erreicht werden. So können z. B. benutzerdefinierte Typen, kollektionswertige Attribute und Referenzen zur leichten Verständlichkeit der Modellierung von temporalen Informationen beitragen. Die Möglichkeit der Kapselung von Objekten samt zugehöriger Methoden sollte einer guten Wart- und Erweiterbarkeit zuträglich sein. 1.2 Gliederung der Arbeit Im nachfolgenden Kapitel sollen zunächst einige Begriffe erläutert werden, die für den weiteren Verlauf der Arbeit benötigt werden. Dazu gehören Konzepte, die vielen bisherigen Ansätzen von temporalen Datenmodellen und temporalen Anfragesprachen gemein sind. Weiterhin werden grundsätzliche Anforderungen zur temporalen Erweiterung von Datenmodellen beschrieben. Im Kapitel drei wird der Sprachvorschlag ATSQL2 anhand von Anwendungsbeispielen und durch Beschreibung des zugrundeliegenden Datenmodells vorgestellt. Darauf aufbauend wird mit Hilfe der Anforderungen des zweiten Kapitels die neue Anfragesprache SQLTE entwickelt. 1.2. GLIEDERUNG DER ARBEIT 3 Nach der Festlegung der neuen temporalen Datenbanksprache wird im vierten Kapitel deren prototypische Implementation vorbereitet. Dazu werden erst allgemein objekt-relationale Möglichkeiten von Oracle 8 vorgestellt. Weiterhin wird anhand zweier Speichermodell-Varianten die konkrete Verwendbarkeit dieser Möglichkeiten geprüft. Es wird dann ein Architekturkonzept temporaler Spracherweiterungen beschrieben, um abschließend erste Vereinbarungen für die darauffolgende Implementation zu treffen. Im fünften Kapitel erfolgt die Beschreibung der eigentlichen Implementation. Dazu wird zunächst eine Übersicht über das gesamte Programm gegeben. Danach werden die Strukturen innerhalb der Datenbank erläutert und das Übersetzungsprogramm außerhalb der Datenbank vorgestellt. Schließlich wird über erste praktische Erfahrungen mit der Anwendung des Prototypen auf umfangreicheren Datenmengen berichtet. Wie üblich wird zum Schluß der Arbeit in Kapitel sechs ein Ausblick auf sinnvolle Erweiterungen gegeben. Es wird dabei sowohl auf denkbare Ergänzungen des Sprachumfangs wie auf mögliche Weiterentwicklungen der Implementation eingegangen. Kapitel 2 Grundbegriffe Es sollen nun einige Begriffe rund um temporale Datenbanken erläutert werden, die als Grundlage für die nachfolgenden Kapitel benötigt werden. Es wird jeweils die Literaturstelle angegeben, unter der detailliertere Informationen zu dem entsprechenden Begriff zu finden sind. Die Quellenangabe besagt also nicht notwendigerweise, daß die erstmalige Definition des jeweiligen Begriffs auf den entsprechenden Autor zurückgeht. 2.1 2.1.1 Temporale Bezeichnungen Dimensionen der Zeit Die übliche Darstellung von Zeit in nicht-temporalen DBMS geschieht mit Hilfe von speziellen Datentypen, z. B. DATE in Oracle mit den Informationen von Jahr, Monat, Tag, Stunden, Minuten und Sekunden. Diese Form wird als benutzerdefinierte Zeit (user-defined time) [Ste98a] bezeichnet und steht dem Benutzer im allgemeinen auch in temporalen DBMS zur Verfügung. Die benutzerdefinierte Zeit wird in Form von entsprechenden Attributen explizit vom Benutzer eingerichtet und direkt unter seiner Regie abgefragt oder verändert. Dazu stehen arithmetische Operationen und Umwandlungsfunktionen für die Zeit-Datentypen zur Verfügung. Es gibt jedoch keinerlei inhärente Interpretation der benutzerdefinierten Zeitinformation durch das DBMS. Ein Beispiel für ein solches explizites Attribut ist etwa das Attribut Geburtsdatum. Im Gegensatz dazu stehen implizite Attribute, d. h. zusätzliche Zeitinformationen, die vom temporalen DBMS ohne direktes Einwirken des Benutzers vergeben werden und durch die temporale Funktionalität der Anfragesprache interpretiert werden. Bei der Benutzung von Datenbanken ist es ein gängiges Vorgehen, Informationen nicht unmittelbar zum Zeitpunkt ihres Eintretens in der Datenbank abzulegen. So wird z. B. ein neuer Angestellter einer Firma erst zwei Tage nach seinem Einstellungsdatum als Mitarbeiter in der Datenbank aufgenommen, weil die zuständige Verwaltungskraft vorher Urlaub hatte. Ebenso ist es denkbar, eine Information in die Datenbank einzutragen, noch bevor diese gültig wird, wenn mit dem Eintreten eines Sachverhalts in der Zukunft fest gerechnet wird. Der neue Angestellte könnte z. B. bereits bei Vertragsabschluß einige Wochen vor dem Einstellungsdatum in die Datenbank eingetragen werden. Es sind daher in der Regel zwei Dimensionen der Zeit von Interesse: Erstens die Gültigkeitszeit (valid time) [ZCF97, ch. 5], nämlich die Zeitspanne, während der eine bestimmte Information 4 2.1. TEMPORALE BEZEICHNUNGEN 5 in der realen Welt zutraf, zutrifft oder zutreffen wird. Zweitens interessiert die Transaktionszeit (transaction time) [ZCF97, ch. 5 ], welche die Zeitspanne beschreibt, während der eine bestimmte Information der realen Welt in der Datenbank dokumentiert war oder ist. Dabei wird die Transaktionszeit ohne Einflußmöglichkeit des Benutzers direkt vom temporalen DBMS vergeben, so daß sich eine Dokumentations- oder sogar Kontrollfunktion ergibt. Außerdem kann so die Transaktionszeit niemals in der Zukunft liegen. Wird sowohl die Gültigkeitszeit als auch die Transaktionszeit unterstützt, so verwendet man den Begriff bitemporal. Wird gar keine eingebaute temporale Unterstützung geboten (abgesehen von der benutzerdefinierten Zeit), ist die Bezeichnung nicht-temporal üblich. Der Begriff Schnappschuß (snapshot) wird sowohl für temporale Datenbanken benutzt, wenn Informationen nur für einen bestimmten Zeitpunkt betrachtet werden, wie auch für nicht-temporale Datenbanken, die naturgemäß die Anwendungswelt nur für einen einzigen Zeitpunkt abbilden. Um Mißverständnisse zu vermeiden, soll hier auf die zuletzt genannte Verwendung des Begriffs Schnappschuß zugunsten der Bezeichnung nicht-temporal verzichtet werden. Datenbanken, die ausschließlich das Prinzip der Gültigkeitszeit unterstützen, werden auch als historische Datenbanken (historical databases) bezeichnet. Wird in einer Datenbank nur die Transaktionszeit benutzt, nennt man diese Rollback-Datenbank (rollback database) [Ste98a], da dort die Datenablage nach dem Prinzip append-only funktioniert und daher stets auf alle vorherigen Datenbankzustände zurückgegriffen werden kann. Obwohl sich die vorliegende Arbeit an der in Abschnitt 1.1 erwähnten bitemporalen Anfragesprache ATSQL2 orientiert, wird der Umfang hier auf ein überschaubares Maß beschränkt, indem der Schwerpunkt von vornherein auf die Unterstützung der Gültigkeitszeit gelegt wird. Das heißt es wird hier auf die Realisierung der Transaktionszeit verzichtet, da die prinzipiellen temporalen Funktionen der Gültigkeitszeit zunächst von größerem Interesse erscheinen als die eher dokumentarischen Aufgaben der Transaktionszeit. Trotzdem soll die Möglichkeit der späteren Ergänzung der Anfragesprache nicht gänzlich außer Acht gelassen werden, so daß in zukünftigen Projekten ggf. die Unterstützung der Transaktionszeit hinzugefügt werden kann. Es wird daher im folgenden häufig nur mit den Begriffen gültig oder Gültigkeitszeit gearbeitet, auch wenn sich die Aussage ebensogut auf allgemeine temporale Informationen und insbesondere auf die Transaktionszeit anwenden läßt. Auch ist oft eine Erweiterung auf bitemporale Informationen denkbar. 2.1.2 Darstellung der Zeit In [ZCF97, ch. 5.2] findet man verschiedene Möglichkeiten beschrieben, den Verlauf der Zeit darzustellen. Hier soll stets das lineare Modell (linear model) verwendet werden. Durch das Fortschreiten der Zeit von der Vergangenheit zur Zukunft ergibt sich dabei eine totale Ordnung auf der Menge der Zeitpunkte. Diese Betrachtungsweise steht im Gegensatz zum verzweigenden Modell (branching model), welches linear ist von der Vergangenheit bis zum aktuellen Zeitpunkt und für die Zukunft verschiedene Zeitlinien zuläßt, die jeweils eine mögliche Folge von Ereignissen darstellen. Im linearen Zeitmodell kann man den Verlauf der Zeit durch verschiedene Dichten beschreiben. Man bezeichnet dabei das Zeitmodell als diskret (discrete), wenn die Menge der Zeitpunkte isomorph zur Menge der natürlichen Zahlen ist, so daß jeder Zeitpunkt genau einen Vorgänger und Nachfolger hat. Bei Isomorphie zu den rationalen oder reellen Zahlen bezeichnet man das Modell als dicht (dense) — zwischen zwei Zeitpunkten läßt sich dann stets ein weiterer finden. 6 KAPITEL 2. GRUNDBEGRIFFE Stetige Modelle (continuous models) sind isomorph zu den reellen Zahlen und somit dicht und lückenlos. Beim diskreten Zeitmodell repräsentiert jede natürliche Zahl einen unteilbaren Zeitabschnitt beliebiger, aber fester Länge. Einen solchen Zeitabschnitt bezeichnet man als Chronon; er stellt die kleinste darstellbare Zeiteinheit des diskreten Zeitmodells dar. Die Länge der Chronons wird durch die Granularität des Modells bestimmt. Spricht man im diskreten Zeitmodell von einem Zeitpunkt eines bestimmten Ereignisses, so meint man tatsächlich den zugehörigen Chronon, d. h. man benennt nur den kleinsten im Modell vorhandenen Zeitabschnitt, währenddessen das Ereignis eingetreten ist. Ein Kalender stellt bestimmte Granularitäten zur Verfügung und ermöglicht die Übersetzung zwischen den einzelnen vorgesehenen Granularitäten. In SQL-92 wird z. B. eine Variante des Gregorianischen Kalenders verwendet: Es existieren die Granularitäten Jahre, Monate, Tage, Stunden, Minuten, Sekunden und Bruchteile von Sekunden sowie die jeweiligen Abbildungsfunktionen. Sofern eine Unterstützung verschiedener Kalender vorhanden ist, wird sich die Auswahl eines bestimmten Kalenders unmittelbar durch die Art der Anwendung ergeben. Die gewünschte Granularität der Einteilung hängt von der benötigten Genauigkeit ab: Während man im üblichen Sprachgebrauch mit einer Minute als Chronon auskommen wird (z. B. ’der Unfall geschah um 12:34 Uhr ’), sind bei technischen Anwendungen sicher feinere Einteilungen vonnöten (z. B. ’der Prozeß wurde um 12:34:56.7890 Uhr verdrängt’). Wenngleich unser Zeitempfinden am ehesten dem der stetigen Modelle entspricht, soll in der vorliegenden Arbeit stets das diskrete Zeitmodell Verwendung finden. Man folgt bei dieser Wahl praktischen Gesichtspunkten, da man bei einer digitalen Abbildung der Zeitlinie letztlich auf eine diskrete Darstellung angewiesen ist. Durch die freie Wahl der Granularität in Abhängigkeit der Meßgenauigkeit der vorhandenen temporalen Informationen sollte diese Zeitdarstellung jedoch stets den Anforderungen genügen. Abschließend sei darauf hingewiesen, daß nachfolgend dem üblichen Sprachgebrauch folgend stets von Zeitpunkten die Rede ist, obwohl tatsächlich derjenige Chronon gemeint ist, der den entsprechenden Zeitpunkt überlappt. 2.2 Zeitstempel Wenn man nicht-temporale Daten um eine Zeitinformation ergänzt, so bezeichnet man diese zusätzliche Information als Zeitstempel (time stamp). Dabei können die Daten mehrere Zeitstempel erhalten, wenn mehrere Zeitlinien unterstützt werden, z. B. wird bei einem bitemporalen DBMS jeweils ein Stempel für die Gültigkeitszeit und einer für die Transaktionszeit vergeben. Zeitstempel werden üblicherweise mit Hilfe von impliziten Attributen realisiert. Die Art der verwendeten Zeitstempel ist eine wesentliche Designentscheidung für ein temporales DBMS. Man unterscheidet daher die Art der Zeitstempel erstens durch ihren Inhalt, d. h. man prüft, welchen Umfang die gestempelten Informationen haben. Zweitens interessiert es, auf welche Einheiten von Daten die Zeitstempel jeweils angewendet werden, d. h. man unterscheidet nach Umfang der gestempelten Daten. Eine Übersicht über verschiedene in der Literatur vorgestellten Datenmodelle mit unterschiedlichen Zeitstempeln findet man z. B. in [ZCF97, ch. 5.4.3] oder [Ste98a, ch. 3]. 2.2. ZEITSTEMPEL 7 i) Zeitpunkte ii) Temporale Intervalle iii) Temporale Elemente iv) Temporale Mengen 4000 | 87 (null) | 91 4000 | 97 5000 | 98 4000 | [87, 90) 4000 | [97, 98) 5000 | [98, ∞) 4000 | [87, 90) ∪ [97, 98) 6000 | [98, ∞) 4000 | {87, 88, 89, 97} 6000 | {98, . . .} Tabelle 2.1: Verschiedene Inhalte von Zeitstempeln am Beispiel einer Gehaltsentwicklung 2.2.1 Inhalt der Zeitstempel Für den Inhalt der Zeitstempel gibt es viele vorstellbare Varianten; hier sollen vier wichtige davon erwähnt werden. Der einfachste Zeitstempel besteht aus der Angabe eines einzigen Zeitpunktes, der z. B. den Beginn der Gültigkeit eines bestimmten Datums zu diesem Zeitpunkt beschreibt. Es bedarf dann eines späteren Zeitpunktes, an dem sich das Datum ändert, um auszudrücken, daß die Gültigkeitszeit des ersten Datums endet. Das geänderte Datum fungiert dann praktisch als Nachfolger. Ist ein solcher nicht vorhanden und die Geschichte des Datums trotzdem unterbrochen, kann man dieses mit Hilfe spezieller Nullwerte darstellen. Wegen dieser Problematik kann ein Stempeln durch ein temporales Intervall (s. Definition 1) sinnvoll sein. Dabei bedarf es keiner Nachfolger, da die Zeitspanne durch einen Beginn- und Endzeitpunkt festgelegt wird. Es werden rechts offene, links abgeschlossene Intervalle verwendet (rola). Diese Darstellung wird in der Literatur häufig benutzt (z. B. [Ste98a, Sno00]), obwohl im Sprachgebrauch eher abgeschlossene Intervalle üblich sind (z. B. ’die Praxis ist von Montag bis Freitag geöffnet’). Sie hat jedoch — wie man leicht nachprüft — den Vorteil, daß die Mengenoperationen Vereinigung und Differenz für endliche rola-Intervalle stets wieder rola-Intervalle liefern. Man erhält so eine einfache und schlüssige theoretische Grundlage, wenngleich man bei der Verwendung des diskreten Zeitmodells (s. Abschnitt 2.1.2) ebensogut mit abgeschlossenen Intervallen arbeiten könnte, indem man statt der offenen rechten Intervallgrenze den zugehörigen Vorgänger einsetzt (z. B. [87, 90] statt [87, 91), falls ein Chronon einem Jahr entspricht). Für die Darstellung eines Zeitraumes, der auf unbestimmte Zeit gültig ist, findet man in der Literatur verschiedene Varianten. Wie in Tabelle 2.1 zu sehen ist, geschieht dieses bei der Verwendung von Zeitpunkten relativ einfach: Besitzt ein Datum keinen Nachfolger, so ist es bis auf weiteres gültig, d. h. im Beispiel ist das Gehalt in Höhe von 5000 bis zur nächsten Änderung gültig, die in der Zukunft stattfinden wird. Die Darstellung ist in diesem Fall eindeutig, nur ihre Interpretation steht dadurch leider noch nicht fest. Wenn der Angestellte z. B. anfragt ’Wie hoch wird mein Gehalt nächstes Jahr sein?’, kann die Antwort 5000 oder keine Angabe lauten, je nachdem, ob das System implizit einen Nachfolger (5000 | ∞) oder (5000 | now) zugrundelegt. Bei temporalen Intervallen wird der o. g. Umstand durch einen künstlichen Endzeitpunkt erreicht, ähnlich dem impliziten Nachfolger bei Stempeln mit Zeitpunkten. Entsprechend den beiden Interpretationsmöglichkeiten existieren für diesen künstlichen Zeitpunkt im wesentlichen zwei Varianten. Hier soll der Vorgehensweise von [Ste98a] gefolgt werden und durch den Endzeitpunkt ∞ (auch: forever ) die Tatsache ausgedrückt werden, daß ein Datum bis auf weiteres gültig ist. Im obigen Beispiel würde der Angestellte also die Antwort 5000 erhalten. Dabei wird jedem Nutzer klar sein, daß die Datenbank keine seherischen Fähigkeiten besitzt, sondern nur Erwartungen der Anwender für die nächste Zukunft wiedergibt. Häufig wird als künstlicher Endzeitpunkt now (auch: until changed ) benutzt, so z. B. in 8 KAPITEL 2. GRUNDBEGRIFFE [Sno95, BJ96, TJB97, Sno00]. Dieses now beschreibt stets den aktuellen Zeitpunkt und wächst mit der Zeit, so daß bei Intervallen, die auf now enden, keine Aussagen über die Zukunft getroffen werden. Im obigen Beispiel würde der Angestellte somit die Antwort keine Angabe erhalten. Diese Darstellung erfordert jedoch vom temporalen DBMS eine ständige Übersetzung des künstlichen Zeitpunktes now in die aktuelle Systemzeit, während der Zeitpunkt ∞ einfach durch den festen größtmöglichen Zeitpunkt des Systems dargestellt werden kann. Freilich darf — wie bereits oben erwähnt — die Verwendung von ∞ nicht zu der fehlerhaften Folgerung verleiten, daß das zugehörige Datum auch in der Zukunft gültig sein wird (s. [NA93]). Es handelt sich stets nur um eine Erwartung oder Prognose. Der Einwand in [Sno95, ch. 20], daß z. B. ein Angestelltenverhältnis bis zum 31.12.9999 eine sehr optimistische Annahme sei, ist sicherlich berechtigt. Trotzdem erscheint diese Darstellung vertretbar, da man den Aufwand der ständigen Übersetzung des aktuellen Zeitpunktes (s. [TJB97]) vermeidet. Definition 1 (temporales Intervall) Es sei s ein Zeitpunkt einer festgelegten Granularität und e ebenfalls ein solcher Zeitpunkt mit e > s oder ∞. Dann bezeichnet die Menge I = [s, e) = {t | s ≤ t < e} ein temporales Intervall. Der wesentliche Vorteil temporaler Intervalle gegenüber Zeitpunkten als Zeitstempel ist die knappe Darstellung von Mengen aufeinanderfolgender Zeitpunkte in direkter Einheit mit dem eigentlichen Datum. Entwickelt man diesen Gedanken weiter, gelangt man zur Nutzung von temporalen Elementen (s. Definition 2) als Zeitstempel, die eine endliche Vereinigung nicht überlappender Intervalle zulassen. Ein Anwendungsbeispiel ist in Tabelle 2.1 dargestellt. Weiterhin sollen die beteiligten Intervalle einander nicht berühren. Durch diese Einschränkungen ist die Definition hier etwas spezieller als in der Literatur üblich. Sie ist gewissermaßen die gekürzte Darstellung der allgemeinen Version, bei der beliebige temporale Intervalle vereinigt werden, auch wenn diese einander berühren oder sich überlappen. Durch die Vereinigungsoperation werden aber in beiden Fällen stets dieselben Zeitabschnitte beschrieben. Die gekürzte Darstellung garantiert jedoch die Eindeutigkeit der Darstellung, d. h. ein Zeitabschnitt kann nicht durch zwei verschiedene temporale Intervalle gemäß Definition 2 beschrieben werden. Definition 2 (temporales Element) Es seien I1 , . . . , In disjunkte temporale Intervalle ohne gemeinsame Berührpunkte. Dann bezeichnet die Menge T E = I1 ∪ . . . ∪ In ein temporales Element. Die temporalen Elemente haben gegenüber Intervallen neben der erhöhten Ausdruckskraft den Vorteil, daß sie — wie man leicht überprüft — gegen endliche Anwendung der mengentheoretischen Operationen Vereinigung und Differenz abgeschlossen sind, sie also nicht aus der Menge der temporalen Elemente herausführen. Dieser Umstand erscheint für die Berechnung von Anfrageergebnissen nützlich. Andererseits lassen sich beliebige temporale Elemente offensichtlich nicht mehr atomar im Sinne der ersten Normalform (1NF) darstellen, was die Handhabung der Zeitstempel unter Umständen komplizieren kann. Eine weitere Form des Zeitstempels ist die der temporalen Menge, bei der einfach alle beteiligten Zeitpunkte (genauer: alle Chronons einer festgelegten Granularität) aufgezählt werden. Da die Mengen nicht auf endlich viele Elemente beschränkt sind, ist die Darstellung von unendlich oft und periodisch wiederkehrenden Ereignissen möglich. Obwohl diese Art der Zeitstempel für den Fall der endlichen Menge nur eine andere Darstellungsform des temporalen Elementes 2.2. ZEITSTEMPEL 9 ist, lassen sich auch dann durch spezielle Implementationen u. U. günstige Eigenschaften erzielen. So wäre es z. B. vorstellbar, eine endliche temporale Menge mit der Granularität eines Tages für die Zeitspanne von 100 Jahren mit Hilfe eines nur fünf Kilobyte großen Bit-Feldes zu implementieren. In Tabelle 2.1 sind die vier vorgestellten Zeitstempel anhand der Entwicklung des Datums Gehalt beispielhaft dargestellt. 2.2.2 Anwendung der Zeitstempel Obwohl im Prinzip ein Stempeln aller möglichen Einheiten von Daten (einzelne Attribute, ganze Tupel oder Objekte, sogar komplette Datenbanken) denkbar ist, wird der Umfang der gestempelten Daten für das relationale Datenmodell vorwiegend nach Zeitstempeln auf Tupeln (tuple timestamping) und auf Attributen (attribute timestamping) unterschieden. Bei Zeitstempeln auf Tupeln werden alle Tupel einer nicht-temporalen Relation durch einen Stempel mit temporalen Informationen ergänzt. Ein solches erweitertes Tupel bezeichnet dann z. B. den Umstand, daß alle expliziten Informationen des Tupels in der Anwendungswelt zu der implizit angegebenen Zeit gültg waren, sind oder sein werden. Nachfolgend soll ht k V T i ein Tupel mit den expliziten Attributen t und dem Zeitstempel V T bezeichnen. Das wesentliche Problem bei Zeitstempeln auf Tupeln ist, daß sich die temporale Information immer auf das gesamte Tupel bezieht. Verändert sich im Laufe der Zeit nur ein einziges Attribut des Tupels, während alle anderen unverändert bleiben, etwa eine Gehaltserhöhung in einer Angestellten-Relation, so muß ein neues Tupel mit nur einem veränderten Attribut und neuem Zeitstempel erzeugt werden. Das erzeugt Redundanzen innerhalb einer Relation und bewirkt, daß die Geschichte eines Objektes der realen Welt über mehrere Tupel einer Relation verteilt wird. Der letztgenannte Umstand wird in [GV85] als vertikale temporale Anomalie bezeichnet. In Tabelle 2.2 wird die Problematik anhand eines Beispiels dargestellt. Um die Anschaulichkeit zu erhöhen, werden dort temporale Intervalle als Zeitstempel verwendet, obwohl der Effekt vom Inhalt des Zeitstempels unabhängig ist. Der vertikalen temporalen Anomalie kann man z. B. durch Einführung der temporalen Normalform (time normal form, TNF) [NA93] begegnen, nach der Relationen aufgeteilt werden in einen zeitunabhängigen Schlüssel und Attribute, die sich nur synchron ändern dürfen. Dieses Vorgehen fördert jedoch die Ausprägung der horizontalen temporalen Anomalie [GV85]. Dabei verteilt sich, infolge der Dekomposition entlang synchronem Änderungsverhalten der Attribute, die Geschichte eines Objektes der realen Welt über mehrere Tupel verschiedener Relationen. Im Beispiel der Angestellten-Datenbank würde man etwa die Relation mit den Attributen AngNr, Name, Gehalt, Abteilung, Adresse und Telefon u. U. in fünf Relationen aufteilen müssen, wenn man AngNr als temporal invariant annimmt, da sich die übrigen fünf Attribute im Zeitverlauf offensichtlich unabhängig voneinander ändern können. Bei Zeitstempeln auf Attributen wird zu jedem Attributwert die Zeitinformation ergänzt. Ändert sich der Wert eines Attributs, so wird lediglich die neue Ausprägung samt neuer Zeitinformation abgelegt. Dieses wird anhand Tabelle 2.3 verdeutlicht. Dort sind dieselben Informationen wie im vorangehenden Beispiel mit Hilfe von Attribut-Zeitstempeln dargestellt. Verwendet man Zeitstempel auf Attributen, so entfallen die oben beschriebenen Probleme der erhöhten Redundanz und der temporalen Anomalien. Andererseits bedarf es offensichtlich, unabhängig vom Inhalt der verwendeten Zeitstempel, eines Datenmodells, das über die erste 10 KAPITEL 2. GRUNDBEGRIFFE AngNr Name Gehalt VT 12 12 13 Müller Müller Meier 4000 5000 6000 [84, 96) [96, ∞) [87, ∞) Tabelle 2.2: Beispiel einer Relation mit Zeitstempeln auf Tupeln AngNr VT Name VT Gehalt VT 12 [84, 96) Müller [84, 96) 13 [87, ∞) Meier [87, ∞) 4000 5000 6000 [84, 96) [96, ∞) [87, ∞) Tabelle 2.3: Beispiel einer Relation mit Zeitstempeln auf Attributen Normalform hinaus geht (non first normal form: NFNF ). Das heißt es werden kollektionswertige Attribute benötigt, da an die Stelle eines Attributes in nicht-temporalen Relationen endliche Mengen von Paaren aus Attributausprägung und Zeitstempel treten. 2.2.3 Temporale Beziehungen Wie in Abschnitt 2.2.1 gesehen, bieten temporale Intervalle die Möglichkeit, auf einfache Art und Weise Mengen von aufeinander folgenden Zeitpunkten zu beschreiben. Bei einer temporalen Anfragesprache bedarf es darüber hinaus Mittel, um Intervalle geeignet miteinander in Beziehung zu setzen, d. h. sie bezüglich ihrer Lage miteinander zu vergleichen. In [Ste98a] werden dazu dreizehn temporale Beziehungen von Zeitintervallen beschrieben. Diese Beziehungen sind in Tabelle 2.4 dargestellt. Sie decken alle Möglichkeiten ab, wie sich zwei temporale Intervalle zueinander verhalten können. Zeitintervalle entsprechen dabei der Definition 1. Zu einem Intervall I = [s, e) bezeichnen begin und end die zugehörigen Start- bzw. Endpunkte, d. h. es gilt begin(I) = s und end(I) = e. Die Beziehung zwischen zwei Intervallen wird über die Lage der Start- bzw. Endpunkte definiert. Eine kleinere Menge temporaler Beziehungen ist in Tabelle 2.5 dargestellt. Dabei werden nur fünf Beziehungen benötigt, um die gleiche Ausdruckskraft wie in Tabelle 2.4 zu erhalten. Dieses wird erreicht, indem nicht nur Intervalle miteinander verglichen werden, sondern auch Zeitpunkte mit Intervallen sowie Zeitpunkte untereinander. Läßt man bei der Vergleichsbildung noch die Funktionen begin und end auf Intervallen zu, so erhält man das gewünschte Ergebnis. In Tabelle 2.5 bezeichnen I1 und I2 also nicht nur Intervalle, sondern es können auch Zeitpunkte und insbesondere Endpunkte von Intervallen gemeint sein. Die Funktionen begin und end erfahren dabei für die einelementige Menge eines Zeitpunktes tp eine einfache Erweiterung, nämlich begin(tp) = end(tp) = tp. 2.3 Temporale Erweiterung von Datenmodellen Nachdem bisher bereits wichtige Konzepte temporaler Datenbanken vorgestellt wurden, sollen nachfolgend Voraussetzungen zur Erweiterung nicht-temporaler Datenmodelle um temporale 2.3. TEMPORALE ERWEITERUNG VON DATENMODELLEN Temporale Beziehung I1 bef ore I2 I1 af ter I2 I1 during I2 I1 contains I2 I1 overlaps I2 I1 overlapped by I2 I1 meets I2 I1 met by I2 I1 starts I2 I1 started by I2 I1 f inishes I2 I1 f inished by I2 I1 equals I2 Definition durch Endpunkte der Intervalle end(I1 ) < begin(I2 ) end(I2 ) < begin(I1 ) (begin(I1 ) > begin(I2 ) ∧ end(I1 ) ≤ end(I2 ))∨ (begin(I1 ) ≥ begin(I2 ) ∧ end(I1 ) < end(I2 )) (begin(I2 ) > begin(I1 ) ∧ end(I2 ) ≤ end(I1 ))∨ (begin(I2 ) ≥ begin(I1 ) ∧ end(I2 ) < end(I1 )) begin(I1 ) < begin(I2 ) ∧ end(I1 ) > begin(I2 ) ∧ end(I1 ) < end(I2 ) begin(I2 ) < begin(I1 ) ∧ end(I2 ) > begin(I1 ) ∧ end(I2 ) < end(I1 ) end(I1 ) = begin(I2 ) end(I2 ) = begin(I1 ) begin(I1 ) = begin(I2 ) ∧ end(I1 ) < end(I2 ) begin(I1 ) = begin(I2 ) ∧ end(I2 ) < end(I1 ) begin(I1 ) > begin(I2 ) ∧ end(I1 ) = end(I2 ) begin(I2 ) > begin(I1 ) ∧ end(I1 ) = end(I2 ) end(I1 ) = end(I2 ) ∧ begin(I1 ) = begin(I2 ) Tabelle 2.4: Temporale Beziehungen von Intervallen nach [Ste98a] Temporale Beziehung Definition durch Endpunkte der Intervalle I1 precedes I2 I1 overlaps I2 I1 meets I2 I1 contains I2 I1 equals I2 end(I1 ) < begin(I2 ) ∃t ∈ I1 ∧ t ∈ I2 end(I1 ) = begin(I2 ) end(I1 ) ≤ begin(I2 ) ∧ end(I1 ) ≥ end(I2 ) end(I1 ) = end(I2 ) ∧ begin(I1 ) = begin(I2 ) Tabelle 2.5: Temporale Beziehungen von Intervallen und Zeitpunkten nach [Ste98a] 11 12 KAPITEL 2. GRUNDBEGRIFFE Funktionalität beschrieben werden. Diese Voraussetzungen bilden bereits die Grundlagen der Anfragesprache ATSQL2 (s. Abschnitt 3.1). Sie wurden u. a. in [BJ96] formuliert, um einen möglichst nahtlosen Übergang von einem nicht-temporalen zu einem temporal erweiterten Datenmodell zu erreichen. 2.3.1 Aufwärtskompatibilität Ein Datenmodell nach [BJ96] besteht aus zwei Komponenten, nämlich einer Menge von Datenstrukturen und einer Anfragesprache auf diesen Datenstrukturen. Formal notiert wird ein Datenmodell M mit der Menge DS (data structure) aller in M darstellbaren Datenbankzustände und der Menge QL (query language) aller gültigen Ausdrücke der Anfragesprache, die auf einen Datenbankzustand aus DS angewendet werden können. Weiterhin bezeichnet db einen Datenbankzustand und s einen Ausdruck aus der Anfragesprache. Die folgende Definition fordert die rein syntaktische Aufwärtskompatibilität, d. h. es sollen im erweiterten Modell alle Datenbankzustände und alle Ausdrücke des zugrundeliegenden Modells vorhanden sein. Es wird somit gewährleistet, daß eine vorhandene Datenbank (basierend auf dem alten Datenmodell) zusammen mit ihren Applikationen in eine neue temporale Datenbank (basierend auf dem erweiterten Datenmodell) transferiert werden kann und die Applikationen ausführbar bleiben. Definition 3 (syntaktische Aufwärtskompatibilität) [BJ96] Es seien M1 = (DS1 , QL1 ) und M2 = (DS2 , QL2 ) zwei Datenmodelle. Das Modell M1 ist syntaktisch aufwärtskompatibel (syntactically upward compatible) zu dem Modell M2 gdw. 1. ∀db2 ∈ DS2 (db2 ∈ DS1 ) und 2. ∀s2 ∈ QL2 (s2 ∈ QL1 ) Bei dem oben beschriebenen Transfer von einer vorhandenen Datenbank zu einer neuen temporalen Datenbank ist es nicht nur erwünscht, daß alte Applikationen ausführbar bleiben, sondern natürlich auch, daß sie dasselbe Ergebnis liefern. Das heißt vorhandene Ausdrücke sollen in Zukunft syntaktisch korrekt sein und auch semantisch die gleiche Bedeutung haben. Genau dieses wird in Definition 4 gefordert. Dabei bezeichnet hhs(db)iiM das Ergebnis der Anwendung eines Ausdrucks s aus QL auf den Datenbankzustand db aus DS und zwar innerhalb des Datenmodells M = (DS, QL). Definition 4 (Aufwärtskompatibilität) [BJ96] Es seien M1 = (DS1 , QL1 ) und M2 = (DS2 , QL2 ) zwei Datenmodelle. Das Modell M1 ist aufwärtskompatibel (upward compatible) zu dem Modell M2 gdw. 1. M1 ist syntaktisch aufwärtskompatibel zu M2 und 2. ∀db2 ∈ DS2 (∀s2 ∈ QL2 (hhs2 (db2 )iiM2 = hhs2 (db2 )iiM1 )) Bleibt man weiter bei dem oben beschriebenen Transfer, können nun transferierte Ausdrücke der Anfragesprache weiterhin ausgeführt werden, und man wird damit dieselben Ergebnisse erzielen. Darüber hinaus ist es jedoch wünschenswert, auf dem neuen System die nicht-temporalen 2.3. TEMPORALE ERWEITERUNG VON DATENMODELLEN 13 Applikationen um neue temporale Funktionalität zu erweitern, ohne eine komplette Neuentwicklung vornehmen zu müssen. Es wird also eine Koexistenz von alten, nicht-temporalen Programmteilen und neuen, temporal erweiterten Programmteilen gewünscht. Bei einer solchen Koexistenz soll natürlich in den temporalen Programmteilen bereits die neue temporale Funktionalität anwendbar sein, d. h. man will sich dort nicht auf die Verwendung aufwärtskompatibler Ausdrücke beschränken. Man benötigt daher neue, temporal erweiterte Datenbankzustände, die sowohl die Anwendung aufwärtskompatibler Ausdrücke wie auch die Anwendung temporaler Ausdrücke erlauben. Im Falle der Anwendung aufwärtskompatibler Ausdrücke auf temporal erweiterte Datenbankzustände soll sich dabei wiederum das gleiche Ergebnis einstellen wie bei der Verwendung der Ausdrücke auf die ursprünglichen nicht-temporalen Datenbankzustände. Es soll daher T (db) denjenigen temporalen Datenbankzustand beschreiben, der sich für jeden Datenbankzustand db eines nicht-temporalen Datenmodells ergibt, wenn man ihn um die zur Erweiterung des Datenmodells benötigten temporalen Informationen ergänzt. Mit Hilfe dieses Operators T wird in der nachfolgenden Definition nun formalisiert, daß jeder Ausdruck einer nicht-temporalen Anfragesprache, angewandt auf den nicht-temporalen Datenbankzustand db, dasselbe Ergebnis liefert, wie wenn er auf den zugehörigen temporalen Datenbankzustand T (db) angewendet würde. Es wird dabei unterschieden zwischen solchen Ausdrücken, die Anfrage-Operationen nach sich ziehen (QL |query ) und solchen, die Änderungs-Operationen darstellen (QL |update ). Ergebnisse beliebiger Operationen sind allgemein nicht vergleichbar, da eine aufwärtskompatible ÄnderungsOperation auf einem temporalen Datenbankzustand wieder einen temporalen Datenbankzustand als Ergebnis liefert und dieselbe Änderungs-Operation auf dem zugehörigen nicht-temporalen Datenbankzustand wiederum zu einem nicht-temporalen Datenbankzustand führt. Betrachtet man den Operator T am Beispiel einer nicht-temporalen Relation und soll diese im Rahmen der temporalen Erweiterung tupelweise mit temporalen Gültigkeitszeitintervallen gestempelt werden, so könnte man jedes Tupel mit dem Intervall [current, ∞) versehen. Dabei meint current den festen Zeitpunkt der Anwendung von T . Auf diese Weise wären alle Tupel der neuen temporalen Relation vom Zeitpunkt der Erweiterung an bis auf weiteres gültig (s. a. Abschnitt 2.2.1). Definition 5 (temporale Aufwärtskompatibilität) [BJ96] Es seien MT = (DST , QLT ) ein temporales Datenmodell und MS = (DSS , QLS ) ein nichttemporales Datenmodell. T sei der beschriebene Erweiterungsoperator und U = u1 , u2 , . . . , un (n ≥ 0) mit ui ∈ (QLS |update ) eine Folge von Änderungs-Operationen. Das Modell MT ist temporal aufwärtskompatibel (temporally upward compatible) zu dem Modell MS gdw. 1. MT ist aufwärtskompatibel zu MS und 2. ∀dbS ∈ DSS (∀U (∀qS ∈ (QLS |query )(hhqS (U (dbS ))iiMS = hhqS (U (T (dbS )))iiMT ))) Abbildung 2.1 stellt die Forderung der temporalen Aufwärtskompatibilität schematisch dar. Jedes Rechteck steht dort für einen Schnappschuß-Datenbankzustand. In der oberen Zeile (nichttemporales Datenmodell MS ) sind dieses nicht-temporale Datenbankzustände. Es ist dort nur der aktuelle Datenbankzustand vorhanden; die vorherigen Zustände gehen jeweils durch die Änderungsoperation verloren, was die gestrichelten Rechtecke symbolisieren. In der unteren Zeile bildet die Folge von einzelnen Schnappschüssen einen temporalen Datenbankzustand, und 14 KAPITEL 2. GRUNDBEGRIFFE aktueller Schnappschuß } Zeit nicht-temporale DB-Zustände im Datenmodell MS dbS u1 ... un-2 un-1 un U(dbS) qS identische Ergebnisse T qS temporaler DB-Zustand im Datenmodell MT ( T(dbS) u1 ... un-2 un-1 un U(T(dbS)) ) = U(T(dbS)) Abbildung 2.1: Ausführung einer temporal aufwärtskompatiblen Anfrage (nach [BJ96]) die Zwischenzustände bleiben erhalten. Bezüglich des Ergebnisses der Anfrage qS spielt es jedoch keine Rolle, ob diese auf den aktuellen, nicht-temporalen Datenbankzustand oder auf den temporalen Datenbankzustand zum aktuellen Zeitpunkt angewendet wird. Im Falle des obigen Beispiels der temporalen Erweiterung einer nicht-temporalen Relation könnte sich die Reihe von Änderungsoperationen ui mit der abschließenden Anfrage qS wie folgt darstellen: In der Relation Ang mit den Attributen AngN r, N ame und Gehalt sollen 1996 und 1998 zwei fünfprozentige Gehaltserhöhungen dokumentiert werden. Außerdem wird 1999 wegen Einsparungsmaßnahmen eine Kündigung aller Mitarbeiter mit mehr als 4400 Monatsgehalt vorgenommen. In Abbildung 2.2 ist dieses Beispiel dargestellt (vgl. a. Abbildung 2.1). In der unteren Zeile wurde die Relation frühzeitig mittels des Operators T in eine temporale Relation erweitert. Da im weiteren Zeitverlauf jedoch nur aufwärtskompatible Sprachausdrücke (u1 , u2 , u3 ) verwendet wurden, liefert die aufwärtskompatible Anfrage qS zum Abschluß auf beiden Relationen identische Ergebnisse1 . Zu betonen ist bei der temporalen Aufwärtskompatibilität, daß der Operator T das alte Modell MS verläßt, d. h. T (dbS ) ist ein temporaler Datenbankzustand, und hinter den Ausdrücken qS sowie ui verbergen sich neue Operationen mit inhärenter Interpretation temporaler Informationen, die lediglich dasselbe nicht-temporale Ergebnis wie im Modell MS liefern. Erst wenn eine geeignete Interpretation von temporalen Datenbankzuständen für einzelne Zeitpunkte gefunden wurde, können diese neuen Operationen mit Hilfe ihrer nicht-temporalen Gegenstücke definiert werden. Dieses geschieht üblicherweise mit dem Zeitscheiben-Operator τ , der einem temporalen Datenbankzustand zu einem bestimmten Zeitpunkt einen nicht-temporalen Datenbankzustand, eben einen Schnappschuß, zuordnet. Damit ist τ die Umkehrung des Operators T . Die Anfrage qS (U (T (dbS ))) aus der Abbildung 2.1 kann dann durch sein nicht-temporales Gegenstück 1 Bei genauer Betrachtung erweist sich die Abbildung 2.2 als etwas unpräzise, da in der unteren Zeile im ersten Kasten unmittelbar nach der Anwendung des Operators T zum Zeitpunkt 1995 die Zeitstempel [95, ∞) verwendet werden müßten. Die abgebildeten Zeitstempel sind also tatsächlich diejenigen, die jeweils durch die nachfolgende Änderungsanweisung erzeugt werden. Dennoch erscheint die Darstellung geeignet, da der gesamte Informationsgehalt der temporalen Relation besser sichtbar wird. 2.3. TEMPORALE ERWEITERUNG VON DATENMODELLEN 1996 aktueller 1999 Schnappschuß 1998 nicht-temporale Relation Ang 1, Meier, 2000 2, Müller, 3000 3, Boss, 4000 } 1995 } Zeit 15 u1 1, Meier, 2100 2, Müller, 3150 3, Boss, 4200 u2 1, Meier, 2205 2, Müller, 3308 3, Boss, 4410 u3 1, Meier, 2205 2, Müller, 3308 qS identische Ergebnisse T qS temporale Relation AngT ( 1, Meier, 2000 [95,96) 2, Müller, 3000 [95,96) 3, Boss, 4000 [95,96) u1 1, Meier, 2100 [96,98) 2, Müller, 3150 [96,98) 3, Boss, 4200 [96,98) u2 1, Meier, 2205 [98,99) 2, Müller, 3308 [98,99) 3, Boss, 4410 [98,99) u3 1, Meier, 2205 [99,∞) 2, Müller, 3308 [99,∞) ) Abbildung 2.2: Beispiel zur Ausführung einer temporal aufwärtskompatiblen Anfrage qS (τ (U (T (dbS )))) definiert werden. Im Beispiel in Abbildung 2.2 würde die abschließende Anfrage qS auf der temporalen Relation durch die Hintereinanderausführung von τ und der nichttemporalen Anfrage qS erreicht. Der beschriebene Zeitscheiben- oder Schnappschuß-Operator τ bildet die Grundlage für die Definition der Schnappschuß-Reduzierbarkeit temporaler Anfragen im folgenden Abschnitt. 2.3.2 Schnappschuß-Reduzierbarkeit Im vorangegangenden Abschnitt wurden gewissermaßen zwei Klassen von Ausdrücken einer temporalen Anfragesprache bestimmt: Erstens die aufwärtskompatiblen Ausdrücke mit nichttemporaler Funktionalität auf nicht-temporalen Datenbankzuständen und zweitens die temporal aufwärtskompatiblen Ausdrücke mit nicht-temporaler Funktionalität auf temporalen Datenbankzuständen. Was eine temporale Erweiterung eines Datenmodells jedoch offensichtlich ausmacht, sind leistungsfähige temporale Operationen auf temporalen Datenbankzuständen. Diese sollen nachfolgend in einer weiteren Klasse festgelegt werden. Dazu wird der bereits erwähnte ZeitscheibenOperator τ benutzt, der für einen Zeitpunkt c und einen temporalen Datenbankzustand dbT den Schnappschuß τc (dbT ) liefert. Dieser Schnappschuß enthält genau die Informationen aus dbT , die zum Zeitpunkt c gültig sind. Die folgende Definition der Schnappschuß-Reduzierbarkeit fordert nun von einem Sprachausdruck eines temporal erweiterten Datenmodells, daß sich ein nicht-temporaler Ausdruck im ursprünglichen Modell finden läßt, so daß es keine Rolle spielt, ob man zuerst den Schnappschuß bildet und dann den nicht-temporalen Ausdruck anwendet oder zuerst den temporalen Ausdruck anwendet und dann den Schnappschuß bildet. Dadurch wird eine leichte Erlernbarkeit der temporalen Erweiterungen in der Anfragesprache ermöglicht, wenn man davon ausgeht, daß der Benutzer mit der zugrundeliegenden nichttemporalen Sprache, etwa SQL-92, vertraut ist. Denn der dem Anwender bekannte, nicht-temporale Ausdruck sS liefert ja, bezüglich jeweils eines Zeitpunktes, stets dasselbe Ergebnis wie der 16 KAPITEL 2. GRUNDBEGRIFFE Schnappschuß c } Zeit temporaler DB-Zustand dbT = ( qT = ( temporaler DB-Zustand qT(dbT) = ( ... τc(dbT) qS qS qS(τc(dbT)) = τc(qT(dbT)) qS qS ) qS ... ) ) Abbildung 2.3: Ausführung einer sequentiellen Anfrage (nach [BJ96]) temporale Ausdruck sT . Definition 6 (Schnappschuß-Reduzierbarkeit) [BJ96, Ste98a] Es seien MT = (DST , QLT ) ein temporales Datenmodell und MS = (DSS , QLS ) ein nichttemporales Datenmodell. Eine temporaler Ausdruck sT ∈ QLT ist schnappschuß-reduzierbar (snapshot reducible) auf sS ∈ QLS gdw. ∀dbT ∈ DST (∀c(τc (sT (dbT )) = sS (τc (dbT )))) Ausdrücke der Anfragesprache des erweiterten temporalen Datenmodells, die der Anforderung der Schnappschuß-Reduzierbarkeit genügen, bezeichnet [BJ96] als sequentiell (sequenced). Durch sie wird die eingangs erwähnte dritte Klasse von Ausdrücken einer temporalen Anfragesprache definiert, nämlich die der sequentiellen Erweiterungen (sequenced extensions). Dieser Begriff soll anhand von Abbildung 2.3 am Beispiel der Ausführung einer sequentiellen Anfrage verdeutlicht werden: Jedes Rechteck stellt wiederum einen Schnappschuß-Datenbankzustand dar; jeder temporale Datenbankzustand besteht aus einer Menge solcher Zeitscheiben. In der Abbildung wird auf den temporalen Datenbankzustand dbT (obere Zeile) die Anfrage qT angewandt, was qT (dbT ) als Ergebnis liefert (untere Zeile). Die temporale Anfrage qT wird gewissermaßen durch die sequentielle Anwendung von qS auf jeden Schnappschuß ausgeführt, daher liegt der Begriff der sequentiellen Anfrage nahe. Über die Schnappschuß-Reduzierbarkeit hinaus wäre es hilfreich, einen Zusammenhang bezüglich der Syntax zwischen zwei Ausdrücken sS und sT herzustellen, wenn sT schnappschußreduzierbar auf sS ist. Zwei Anfragen, die bezüglich ihrer Semantik für den Anwender ähnlich sind, da sie betrachtet auf einzelnen Zeitscheiben dasselbe Ergebnis liefern, sollen auch syntaktisch nur eine möglichst geringe Abweichung zeigen, damit der Benutzer die temporalen Anfragen leichter formulieren kann. Dieser Idee soll die folgende Definition Rechnung tragen. Definition 7 (syntaktisch ähnliche schnappschuß-reduzierbare Erweiterung) [BJ96] Es seien MT = (DST , QLT ) ein temporales Datenmodell und MS = (DSS , QLS ) ein nichttemporales Datenmodell. Das Datenmodell MT ist eine syntaktisch ähnliche schnappschuß-reduzierbare Erweiterung (syntactically similar snapshot-reducible extension) von MS gdw. 1. ∀sS ∈ QLS ∃sT ∈ QLT , so daß qT schnappschuß-reduzierbar auf sS ist 2.3. TEMPORALE ERWEITERUNG VON DATENMODELLEN 17 2. es gibt zwei Zeichenketten S1 und S2 derart, daß für jeden Ausdruck sT ∈ QLT , der schnappschuß-reduzierbar auf sS ist, gilt: sT ist syntaktisch identisch ist zu S1 sS S2 In der vorangehenden Definition können die beiden Zeichenketten S1 und S2 auch leer sein, jedoch wird man im allgemeinen einen Widerspruch zur temporalen Aufwärtskompatibilität erhalten, wenn beide Zeichenketten zugleich leer sind: Unter der sinnvollen Annahme, daß der temporale Ausdruck sT ein temporales Ergebnis liefert, kann nicht die syntaktisch identische Anfrage sS ein nicht-temporales Ergebnis liefern. Insgesamt wurden bisher drei wesentliche Konzepte zur temporalen Erweiterung von Datenmodellen vorgestellt, nämlich die Aufwärtskompatibilität, die temporale Aufwärtskompatibilität und die syntaktisch ähnliche schnappschuß-reduzierbare Erweiterung. Im folgenden Abschnitt sollen weitere Anforderungen unter dem Begriff der temporalen Vollständigkeit gesammelt werden. 2.3.3 Temporale Vollständigkeit Der Begriff der temporalen Vollständigkeit (temporal completeness) wird in [BJS95] benutzt, um Anforderungen an temporale Datenmodelle bezüglich eines zugrundeliegenden nicht-temporalen Modells zu spezifizieren. Wie dort selbst betont wird, hat die temporale Vollständigkeit nichts gemein mit der üblichen Bezeichnung der Vollständigkeit, mit der die Ausdrucksfähigkeit einer Sprache im Vergleich zur Relationenalgebra (siehe z. B. [Vos99, Kap. 10]) gemessen wird. Zunächst benötigt man den Begriff der temporalen Semi-Vollständigkeit (temporal semicompleteness), den man mit Hilfe der Definitionen aus dem vorangehenden Abschnitt leicht formulieren kann. Als erstes wird dabei gefordert, daß man im temporalen Datenmodell jeden Datenbankzustand des zugehörigen nicht-temporalen Datenmodells durch Bildung einer Zeitscheibe zu einem bestimmten Zeitpunkt erzeugen kann. Damit wird sichergestellt, daß bei der temporalen Erweiterung keine Informationen verloren gehen. Durch die temporale Aufwärtkompatibilität und den Operator T aus dem vorangehenden Abschnitt wäre diese Forderung bereits garantiert. Zweitens benötigt man die syntaktisch ähnliche Schnappschuß-Reduzierbarkeit der Modellerweiterung. Definition 8 (semi-temporale Vollständigkeit) [BJS95] Es seien MT = (DST , QLT ) ein temporales Datenmodell und MS = (DSS , QLS ) ein nichttemporales Datenmodell. Das Datenmodell MT ist semi-temporal vollständig (temporally semicomplete) bezüglich MS gdw. 1. ∀dbS ∈ DSS ∃dbT ∈ DST ∧ ∃c (dbS = τc (dbT )) 2. MT ist eine syntaktisch ähnliche schnappschuß-reduzierbare Erweiterung von MS . Bei der sequentiellen Erweiterung bringt es das Prinzip der Schnappschuß-Reduzierbarkeit mit sich, daß z. B. das Ergebnis der Anfrage zu einem bestimmten Zeitpunkt ausschließlich aus Informationen des Datenbankzustandes zu eben diesem Zeitpunkt abgeleitet wird (s.a. Abbildung 2.3). Weitergehende Anfragen, die zu jedem Zeitpunkt Informationen aus möglicherweise allen Zeitpunkten des Datenbankzustandes benutzen, sind nach dem beschriebenen Prinzip der sequentiellen Anfrageverarbeitung nicht realisierbar. Für diese Anforderungen bedarf es der 18 KAPITEL 2. GRUNDBEGRIFFE Zeit temporaler DB-Zustand dbT = ( ... qT = ( temporaler DB-Zustand qT(dbT) = ( ) ) ... ) Abbildung 2.4: Ausführung einer nicht-sequentiellen Anfrage (nach [BJ96]) Unterstützung von nicht-sequentiellen Anfragen (non-sequenced queries) durch die temporale Erweiterung des Datenmodells. Übertragen auf allgemeine Ausdrücke der Anfragesprache benötigt man die Klasse der nicht-sequentiellen Erweiterungen (nonsequenced extensions). Die Ausführung einer nicht-sequentiellen Anfrage soll anhand von Abbildung 2.4 erläutert werden. Jedes Rechteck stellt wie gehabt einen Schnappschuß-Datenbankzustand dar, jeder temporale Datenbankzustand besteht aus einer Menge solcher Zeitscheiben. In der Abbildung wird auf den temporalen Datenbankzustand dbT (obere Zeile) die Anfrage qT angewandt, was qT (dbT ) als Ergebnis liefert (untere Zeile). Zur Erzeugung des Ergebnisses zu einem festen Zeitpunkt werden Informationen aus möglicherweise allen Zeitpunkten des Zustandes dbT benötigt. Eine sequentielle Anwendung von qS für jeden Schnappschuß von dbT ist nicht mehr möglich. Mit der Erläuterung der nicht-sequentiellen Erweiterungen wurden alle benötigten Begriffe zur Definition der temporalen Vollständigkeit eines Datenmodells beschrieben. Dennoch soll hier auf eine formale Definition verzichtet werden, da diese in [BJS95] durch den direkten Bezug auf das relationale Datenmodell und die Anfragesprache SQL hier zu speziell erscheint. Stattdessen wird dieses Kapitel mit der Aufzählung von drei nützlichen Anforderungen der temporalen Vollständigkeit an eine temporale Erweiterung MT eines Datenmodells MS geschlossen. 1. MT ist semi-temporal vollständig bezüglich MS . 2. MT bietet nicht-sequentielle Erweiterungen der Anfragesprache, die über die Schnappschuß-Reduzierbarkeit hinaus gehen. Dabei gibt es wie in Definition 8 zwei Zeichenketten, die jeden nicht-temporalen Ausdruck der Sprache zu einem nicht-sequentiellen ergänzen. 3. In MT werden zum Vergleich temporaler Informationen die Beziehungen aus Tabelle 2.4 zur Verfügung gestellt. Kapitel 3 Temporale Erweiterungen von SQL Es existiert inzwischen eine Vielzahl von Vorschlägen für temporale Anfragesprachen, davon sind wegen der weiten Verbreitung von SQL natürlich ein Großteil temporale Erweiterungen von SQL. Wie bereits in der Einleitung erwähnt, soll hier die Sprache ATSQL2 [BJS95] vorgestellt werden, da für sie sowohl umfangreiche theoretische Grundlagen als auch praktische Erfahrungen durch die Entwicklung von Prototypen bestehen (s. a. Abschnitte 1.1 und 3.1). Im Rahmen dieser Vorstellung werden sich für die Entwicklung einer eigenen temporalen Erweiterung zusammen mit den Grundlagen aus dem vorangegangenen Kapitel einige Punkte ergeben, die veränderungswürdig erscheinen. Es soll daher abschließend eine neue temporale Erweiterung von SQL mit dem Namen SQLTE (structured query language temporal extended) vorgestellt werden, die sich zwar in weiten Teilen an ATSQL2 orientiert, jedoch auch einige wesentliche Unterschiede aufweist. 3.1 Der Sprachvorschlag ATSQL2 Bei ATSQL2 (applied temporal structured query language) [BJS95, SBJ96a, SBJ96b] handelt es sich um eine bitemporale Datenbanksprache, die neben der temporalen Anfragesprache auch Datenmodifikation (DML), Datendefinition (DDL) sowie die Formulierung von temporalen Integritätsbedingungen unterstützt. Nach [Ste98a, ch. 4] ist ATSQL2 das Ergebnis der Integration von drei unterschiedlichen Ansätzen zu temporalen Anfragesprachen, nämlich TSQL2 [SAA94a, SAA94b, ZCF97], ChronoLog [Böh94] und Bitemporal ChronoSQL (entwickelt von A. Steiner, siehe z. B. [Pul95]). Die Bezeichnung applied im Namen von ATSQL2 weist schon auf die wesentliche Veränderung gegenüber TSQL2 hin: Es handelt sich um eine anwendungsbezogene Sprache, die das Datenmodell der mengenwertigen Relationen aufgibt, um stattdessen der Praxis von Duplikaten zu entsprechen. Weiterhin wird neben der reinen Aufwärtskompatibilität gegenüber SQL-92 auch die temporale Aufwärtskompatibilität (s. Abschnitt 2.3.1) gefordert, um einen einfachen Wechsel von der konventionellen zur temporalen Datenbank und eine Koexistenz von vorhandenen (nicht-temporalen) und neuen (temporalen) Programmteilen zu ermöglichen. Schließlich soll ATSQL2 den unter dem Begriff der temporalen Vollständigkeit (s. Abschnitt 2.3.3) gesammelten Ansprüchen gerecht werden. 19 20 KAPITEL 3. TEMPORALE ERWEITERUNGEN VON SQL Diesen Forderungen folgend ergibt sich in [BJS95, SBJ96a, SBJ96b] der Sprachvorschlag ATSQL2 in den folgenden vier Stufen der Erweiterung gegenüber SQL-92: • Ebene 1: upward compatibility — Es werden nicht-temporale Sprachkonstrukte auf nichttemporale Tabellen angewendet. Die Funktionalität ist mit der von SQL-92 identisch. • Ebene 2: temporal upward compatibility — Es werden nicht-temporale Sprachkonstrukte auf temporale Tabellen angewendet. Die Funktionalität ist mit der von SQL-92 identisch, jedoch unter Benutzung von temporalen Tabellen, die nur für den aktuellen Zeitpunkt ausgewertet werden. • Ebene 3: sequenced extensions — Es werden temporale Sprachkonstrukte auf temporale Tabellen angewendet. Die Auswertung erfolgt sukzessiv für alle vorhandenen Zeitpunkte, d. h. Informationen werden für einen bestimmten Zeitpunkt der Ergebnistabelle jeweils ausschließlich aus genau diesem Zeitpunkt der Quelltabelle(n) abgeleitet. Die Interpretation der Zeitstempel erfolgt implizit durch das DBMS. • Ebene 4: nonsequenced extensions — Es werden ebenfalls temporale Sprachkonstrukte auf temporale Tabellen angewendet. Die Auswertung erfolgt über alle möglichen Zeitpunkte hinweg, d. h. Informationen werden für einen bestimmten Zeitpunkt der Ergebnistabelle jeweils u. U. aus allen vorhandenen Zeitpunkten der Quelltabelle(n) abgeleitet. Dem Benutzer stehen die Zeitstempel explizit zur Verfügung; das DBMS übernimmt keine implizite Interpretation der Zeitinformationen. Diese vier Ebenen folgen unmittelbar den Modellerweiterungen aus dem Abschnitt 2.3. Für jede dieser vier Ebenen findet man in [SBJ96a, ch. 6] und [BJ96, ch. 3] einige Beispiele in Form einer Quick Tour. Nachfolgend soll nun, ebenfalls entlang der oben beschriebenen vier Ebenen, ein Überblick über die Syntax und die Semantik von ATSQL2 gegeben werden. Wie bereits in Abschnitt 2.1.1 beschrieben, beschränkt sich diese Darstellung auf die Erweiterungen bzgl. der Gültigkeitszeit. 3.1.1 Syntax Die vorliegende Einführung in die Syntax von ATSQL2 orientiert sich an [Ste98a, ch. 4.3], basierend auf dem in [SBJ96a] vorgestellten Standard für SQL/Temporal. Es existieren dabei geringfügige Abweichungen zur Syntax der Spracherweiterung aus [BJ96]. Die Beschreibung erfolgt anhand von Beispielen mit entsprechenden Erläuterungen. Für eine formale Definition der gültigen Syntax sei auf [Ste98b] verwiesen. Für die Ebene der aufwärtskompatiblen Sprachausdrücke bedarf es keiner umfangreichen Beschreibung: Der Definition 4 folgend, werden alle SQL-Befehle weiterhin unterstützt, und diese werden dem Leser vertraut sein. Die nachfolgenden nicht-temporalen Anweisungen dienen somit nur zur Erzeugung und Initialisierung der in diesem Abschnitt verwendeten Beispieltabelle. create table Ang (AngNr number, Name varchar(30), Gehalt number, ChefNr number); insert into Ang values (12, ’Müller’, 5000, 27); insert into Ang values (13, ’Meier’, 6000, 27); insert into Ang values (27, ’Boss’, 5900, 27); 3.1. DER SPRACHVORSCHLAG ATSQL2 21 select a1.AngNr, a1.Name, a1.Gehalt, a2.Name as Chef from Ang a1, Ang a2 where a1.ChefNr = a2.AngNr and a1.Gehalt > 5900; =⇒ a1.AngNr a1.Name a1.Gehalt Chef 13 Meier 6000 Boss Bei der so angelegten Tabelle mit Informationen über die Angestellen eines Unternehmens sind nur aufwärtskompatible Sprachausdrücke zugelassen wie z. B. die obige Anfrage. Jede Verwendung der Tabelle in Kombination mit temporalen ATSQL2-Befehlen ist unzulässig. Um temporale Funktionalität zu demonstrieren, bedarf es daher einer temporalen Erweiterung der Tabelle mittels alter table. Man erhält so eine temporale Tabelle, die für jedes Tupel die zugehörige Gültigkeitszeit aufnimmt. Diese Zeitabschnitte enden voreingestellt an einem ungewissen Zeitpunkt in der Zukunft und beginnen zum Zeitpunkt der temporalen Aufwertung der Tabelle. Im nachfolgenden Beispiel sei dies der 5. Oktober 1999. alter table Ang add validtime; validtime select * from Ang; =⇒ AngNr Name Gehalt ChefNr VT 12 13 27 Müller Meier Boss 5000 6000 5900 27 27 27 [05.10.1999, ∞) [05.10.1999, ∞) [05.10.1999, ∞) Um die neu hinzugefügten Gültigkeitszeiten darstellen zu können, wird im obigen Beispiel als kleiner Vorgriff bereits eine sequentielle temporale Anfrage verwendet. Durch das Voranstellen des Schlüsselwortes validtime wird die Interpretation als sequentielle Anfrage erreicht. Im Rahmen der temporalen Aufwärtskompatibilität erlaubt die nun temporale Tabelle Ang weiterhin die Anwendung von nicht-temporalen Befehlen. Diese werden dann stets unter Berücksichtigung des Zeitpunktes der Anwendung ausgeführt. Im nachfolgenden Beispiel sei dieses der 12. Oktober 1999. Die erste Anweisung erhöht das Gehalt des Angestellten Boss zum aktuellen Zeitpunkt. Die zweite Anweisung wurde in ihrer nicht-temporalen Version bereits zu Beginn dieses Abschnitts verwendet. Sie liefert hier wegen der inzwischen durchgeführten Gehaltserhöhung ein anderes Ergebnis. update Ang set Gehalt = 6400 where Name = ’Boss’; validtime select * from Ang; =⇒ AngNr Name Gehalt ChefNr VT 12 13 27 27 Müller Meier Boss Boss 5000 6000 5900 6400 27 27 27 27 [05.10.1999, ∞) [05.10.1999, ∞) [05.10.1999, 12.10.1999) [12.10.1999, ∞) select a1.AngNr, a1.Name, a1.Gehalt, a2.Name as Chef from Ang a1, Ang a2 where a1.ChefNr = a2.AngNr and a1.Gehalt > 5900; 22 =⇒ KAPITEL 3. TEMPORALE ERWEITERUNGEN VON SQL a1.AngNr a1.Name a1.Gehalt Chef 13 27 Meier Boss 6000 6400 Boss Boss Man kann sich an dieser Stelle von der Einhaltung der temporalen Aufwärtskompatibilität leicht überzeugen: Die nicht-temporale Tabelle zu Beginn des Abschnitts würde nach der Anwendung der obigen Befehle update und select dasselbe Ergebnis liefern wie die temporal erweiterte Tabelle im Beispiel — lediglich die Gehaltshistorie des Angestellen Boss würde verlorengehen. In den vorangehenden Beispielen wurde die Tabelle Ang zunächst durch nicht-temporale Befehle erzeugt und initialisiert, um dann mit Hilfe von alter table temporal erweitert zu werden. Weiterhin wurden mit der dann temporalen Tabelle einige temporal aufwärtskompatible Befehle ausgeführt. Betrachtet man die Ebene der sequentiellen Erweiterungen, so ist es ebenso möglich, eine temporale Tabelle direkt zu erzeugen und zu initialisieren. Im folgenden Beispiel erhält man die gleiche Tabelle wie nach der letzten temporal aufwärtskompatiblen Anweisung. Wie bereits gesehen, werden Befehle der sequentiellen Erweiterungsebene durch das Schlüsselwort validtime gekennzeichnet. create table Ang (AngNr number, Name varchar(30), Gehalt number, ChefNr number) as validtime; validtime period [date ’05.10.1999’- forever) insert into Ang values (12, ’Müller’, 5000, 27); validtime period [date ’05.10.1999’- forever) insert into Ang values (13, ’Meier’, 6000, 27); validtime period [date ’05.10.1999’- date ’12.10.1999’) insert into Ang values (27, ’Boss’, 5900, 27); validtime period [date ’12.10.1999’- forever) insert into Ang values (27, ’Boss’, 6400, 27); validtime select a1.AngNr, a1.Name, a1.Gehalt, a2.Name as Chef from Ang a1, Ang a2 where a1.ChefNr = a2.AngNr and a1.Gehalt > 5900; =⇒ AngNr Name Gehalt Chef VT 13 13 27 Meier Meier Boss 6000 6000 6400 Boss Boss Boss [05.10.1999, 12.10.1999) [12.10.1999, ∞) [12.10.1999, ∞) Jede gültige nicht-temporale SQL-Anfrage kann durch Voranstellen von validtime als sequentielle Anfrage auf temporalen Tabellen ausgeführt werden, so daß diese der SchnappschußReduzierbarkeit genügt. Ebenso können die Befehle der DML (data manipulation language) insert, update und delete temporal erweitert werden. In allen Fällen ist eine Einschränkung auf bestimmte Intervalle durch Angabe von period möglich, wie im obigen Beispiel zu sehen ist. Weiterhin wird dort an die temporale Tabelle Ang die bereits bekannte Anfrage nach Angestellten mit einem Gehalt über 5900 in Kombination mit ihren Vorgesetzten gestellt, hier jedoch durch das Schlüsselwort validtime in der Version der sequentiellen Anfrage. Es erscheinen dabei für den Angestellten Meier zwei Tupel mit einander berührenden Gültigkeitszeitintervallen. Dieses ist eine Eigenart von ATSQL2, deren Ursprung in der temporalen Variante des kartesischen Produktes liegt. In Abschnitt 3.1.2 wird auf diesen inhaltlichen Aspekt der Sprache näher eingegangen. 3.1. DER SPRACHVORSCHLAG ATSQL2 23 In der Ebene der nicht-sequentiellen Spracherweiterungen stehen dem Anwender die Gültigkeitszeitintervalle der Tupel explizit zur Verfügung, und es werden keine sprachinhärenten Interpretationen dieser Intervalle vorgenommen. So ist es im Gegensatz zu den sequentiellen Anfragen möglich, Ergebnistupel durch Auswertung von Tupeln mit nicht überlappenden Zeitintervallen zu bilden. Zu diesem Zweck können die temporalen Beziehungen aus Tabelle 2.5 benutzt werden sowie die dort verwendeten Funktionen begin und end. Die Funktion validtime liefert zu einem temporalen Tupel das zugehörige Gültigkeitszeitintervall. Die nicht-sequentiellen Befehle werden durch das Voranstellen der beiden Schlüsselwörter nonsequenced validtime gekennzeichnet. Im folgenden Beispiel soll nach allen Angestellten gefragt werden, die in der Vergangenheit eine Gehaltserhöhung erhalten haben. nonsequenced validtime select begin(validtime(a2)) as Datum, a2.Name as Name from Ang a1, Ang a2 where a1.AngNr = a2.AngNr and validtime(a1) meets validtime(a2) and a1.Gehalt < a2.Gehalt; =⇒ Datum Name 12.10.1999 Boss Bisher wurden Beispiele mit Anfragen vorgestellt, die nur Sprachelemente einer temporalen Erweiterungsebene enthalten. Es ist jedoch auch eine Kombination der verschiedenen Spracherweiterungen innerhalb einer Anfrage möglich, nämlich bei der Verwendung von abgeleiteten Tabellen (derived tables oder inline-views) und Subqueries. Bei abgeleiteten Tabellen muß jedoch beachtet werden, welchen Typ die jeweils verwendete Anfrage zurückliefert: Nur sequentielle Anfragen haben ein temporales Ergebnis und können so anstatt einer temporalen Tabelle verwendet werden. Aufwärtskompatible, temporal aufwärtskompatible und nicht-sequentielle Anfragen können dagegen nur anstelle gewöhnlicher, nicht-temporaler Tabellen eingesetzt werden. Das folgende Beispiel macht deutlich, wann eine solche Kombination notwendig sein kann. Es soll dort die Frage beantwortet werden, welche Mitarbeiter seit der Firmengründung am 05.10.1999 ununterbrochen Mitglieder des Unternehmens waren und es noch immer sind. Daß eine sequentielle Anfrage dafür nicht ausreicht, ist offensichtlich: Durch die sequentielle Auswertung einzelner Schnappschüsse kann nicht entschieden werden, ob das jeweilige Beschäftigungsverhältnis die ganze Zeit über bestand. Die folgende — zunächst naheliegende — nicht-sequentielle Anfrage liefert leider nicht das entsprechende Ergebnis, da der Mitarbeiter Boss nicht erscheint. Das liegt an der Festlegung der Anfragesprache, die vom Benutzer angegebenen Intervalle in ihrer ursprünglichen Form zu belassen und keine automatische zeitliche Vereinigung wertgleicher Einträge (coalescing) vorzunehmen. Im nachfolgenden Abschnitt über die Semantik von ATSQL2 wird diese Operation näher erläutert. nonsequenced validtime select * from Ang a where validtime(a) contains period [date ’05.10.1999’-now); =⇒ AngNr Name Gehalt ChefNr 12 13 Müller Meier 5000 6000 27 27 Das gewünschte Ergebnis aller firmentreuen Mitarbeiter erhält man mit dem folgenden abschließenden Beispiel. In der verwendeten abgeleiteten Tabelle werden zunächst alle Mitarbeiter 24 KAPITEL 3. TEMPORALE ERWEITERUNGEN VON SQL über die als zeitinvariant angenommene Angestelltennummer bestimmt. Durch die Angabe von (period) wird die zeitliche Verschmelzung wertgleicher Tupel erreicht, so daß die abgeleitete Tabelle alle Angestelltennummern mit maximalen temporalen Intervallen liefert. Durch den temporalen Join mit der Angestelltentabelle werden die bei der Verschmelzung wegprojizierten Attribute wieder hinzugefügt. Dabei wird mit Hilfe der Bedingung validtime(a1) contains now die aktuelle Version der Angestellteninformation ausgewählt. Die Formulierung validtime(a2) contains period [date ’05.10.1999’-now) stellt schließlich die eigentliche Anforderung sicher, daß die Mitarbeiter ununterbrochen für die Firma tätig waren. nonsequenced validtime select a1.AngNr, a1.Name, a1.Gehalt, a1.ChefNr from Ang a1, (validtime (select AngNr from Ang) (period)) a2 where validtime(a1) contains now and a1.AngNr = a2.AngNr and validtime(a2) contains period [date ’05.10.1999’-now); AngNr Name Gehalt ChefNr 12 13 27 Müller Meier Boss 5000 6000 6400 27 27 27 =⇒ 3.1.2 Semantik Nachdem im vorangehenden Abschnitt versucht wurde, einen Eindruck von der Verwendung der neuen Sprachkonstrukte zu vermitteln, soll nun mit der Erläuterung der inhaltlichen Basis dieser Konstrukte fortgefahren werden. Echte temporale Funktionalität existiert nur in der Ebene der sequentiellen Erweiterungen. Aufwärtskompatible Sprachelemente basieren naturgemäß auf dem relationalen Datenmodell. Gleiches gilt für temporal aufwärtskompatible Anweisungen, nach der Schnappschußbildung auf einer temporalen Tabelle. Die Ebene der nicht-sequentiellen Erweiterungen basiert ebenso auf dem relationalen Datenmodell, wobei die temporalen Intervalle in explizite nicht-temporale Attribute umgewandelt werden. Allein die sequentiellen Anfragen besitzen ein neues Datenmodell — das temporale relationale Datenmodell von ATSQL2. Die vorliegende Beschreibung der temporalen Modellerweiterung orientiert sich weitgehend an [BJ96], verwendet jedoch einige Änderungen gemäß [BJS95, SBJ96a]. Während in den genannten Arbeiten bitemporale Versionen der Modellerweiterung vorgestellt werden, wird hier, wie gehabt, nur auf die Unterstützung der Gültigkeitszeit eingegangen. Ausgehend von den Datenstrukturen des relationalen Modells (eine vollständige formale Definition findet man z. B. in [Vos99, ch. 5]) wird eine nicht-temporale Relation durch Hinzufügen eines Zeitstempels für jedes Tupel zu einer temporalen Relation erweitert. Gemäß Definition 1 werden halboffene Intervalle verwendet, repräsentiert durch die Beginn- und Endzeitpunkte. Abbildung 3.1 zeigt eine Übersicht der bekannten Relationenalgebra mit den Operationen Selektion, Projektion, Vereinigung, Kreuzprodukt und Differenz — eine vollständige formale Definition gibt z. B. [Vos99, ch. 10]. Dabei beschreibt c eine Selektionsbedingung und f eine Einschränkung des Tupels auf bestimmte Attribute. Entsprechend den zugehörigen nicht-temporalen Operationen werden in Definition 9 die Operationen auf temporalen Relationen definiert. Die 3.1. DER SPRACHVORSCHLAG ATSQL2 25 σc (r) = {t | t ∈ r ∧ c(t)} πf (r) = {t1 | ∃t2 (t2 ∈ r ∧ t1 = f (t2 ))} r1 ∪ r2 = {t | t ∈ r1 ∨ t ∈ r2 } r1 × r2 = {t1 ◦ t2 | t1 ∈ r1 ∧ t2 ∈ r2 } r1 \ r2 = {t | t ∈ r1 ∧ t ∈ / r2 } Abbildung 3.1: Nicht-temporale Relationenalgebra temporalen Operationen werden durch das Anhängen von vt gekennzeichnet. Definition 9 (temporale Relationenalgebra) [BJS95, BJ96, SBJ96a] σcvt (r) = {ht k V T i | ht k V T i ∈ r ∧ c(t)} πfvt (r) = {ht1 k V T i | ∃t2 (ht2 k V T i ∈ r ∧ t1 = f (t2 ))} r1 ∪vt r2 = {ht k V T i | ht k V T i ∈ r1 ∨ ht k V T i ∈ r2 } r1 ×vt r2 = {ht1 ◦ t2 k V T i | ht1 k V T1 i ∈ r1 ∧ ht2 k V T2 i ∈ r2 ∧ V T = intersect(V T1 , V T2 ) ∧ V T1 overlaps V T2 } vt r1 \ r2 = {ht k V T i | ∃V T1 (ht k V T1 i ∈ r1 ∧ (∃V T2 (ht k V T2 i ∈ r2 ∧ V T1− ≤ V T2+ ∧ V T − = V T2+ ) ∨ V T − = V T1− ) ∧ (∃V T3 (ht k V T3 i ∈ r2 ∧ V T1+ ≥ V T3− ∧ V T + = V T3− ) ∧ V T + = V T1+ ) ∧ V T − < V T + ∧ 6 ∃V T4 (ht k V T4 i ∈ r2 ∧ V T4 overlaps V T ))} In der Definition 9 wird für zeitgestempelte Tupel die bereits in Abschnitt 2.2.2 verwendete Notation benutzt: ht k V T i bezeichnet ein Tupel mit den expliziten Attributen t und dem Zeitstempel V T . Der Zeitstempel V T steht für ein Intervall der Gültigkeitszeit, bestehend aus Anfangs- und Endpunkt, welche durch V T − und V T + dargestellt werden. Die Beziehung overlaps genügt der Definition aus Tabelle 2.5, die Funktion intersect liefert genau das Schnittintervall von overlaps. Das Symbol ◦ bezeichnet die Konkatenation zweier Tupel, präzise die Konkatenation der expliziten Attribute zweier Tupel. Die ersten drei Operationen sind einfache Erweiterungen ihrer nicht-temporalen Gegenstücke. Zu beachten ist, daß für die Selektionsbedingung c wie für die Projektionsliste f kein Zugriff auf den Zeitstempel vorgesehen ist. Es können also z. B. keine temporalen Auswahlbedingungen formuliert werden. Diese Restriktion ist notwendig, da sonst die resultierenden Anfragen nicht mehr der Schnappschuß-Reduzierbarkeit genügen würden. Dieses liegt einfach daran, daß nach der Schnappschußbildung keine temporalen Informationen mehr zur Verfügung stehen, und es somit nicht möglich ist, ein nicht-temporales Äquivalent zu einer Anfrage mit temporaler Auswahlbedingung zu finden (vgl. Definition 6). An dieser Stelle wird auch das Ergebnis der sequentiellen Beispielanfrage aus dem vorangehenden Abschnitt klar: Das Kreuzprodukt paart alle Tupel, die eine überlappende Gültigkeitszeit haben und bildet aus den expliziten Attributen ein neues Tupel mit der Überlappung als Zeitstempel. Ist ein Objekt in der realen Welt über zwei oder mehrere Tupel verteilt (im Fall des Beispiels der Mitarbeiter Boss wegen einer Gehaltserhöhung), so nehmen alle Versionen des Tupels einzelnd an der Paarbildung teil, sofern ein zeitlicher Überlapp vorhanden ist. Das Ergebnis 26 KAPITEL 3. TEMPORALE ERWEITERUNGEN VON SQL r1 r2 r1\r2 VT1 VT2 VT3 VT Abbildung 3.2: Differenz-Operator der temporalen Relationenalgebra (nach [BJ96]) ist (wie im Beispiel) eine Vielzahl von Ergebnistupeln, wodurch das Ablesen der gewünschten Information nicht unbedingt erleichtert wird. Häufig werden die mehrfach auftretenden Ergebnistupel bezüglich ihrer expliziten Attribute wertgleich sein, so daß eine abschließende zeitliche Verschmelzung (coalesce) Abhilfe schaffen würde. Eine implizite Verschmelzung nach jeder Produktbildung würde jedoch u. U. der Schnappschuß-Reduzierbarkeit widersprechen, da sich — betrachtet für einzelne Schnappschüsse — die Anzahl der Ergebnistupel durch Zusammenfügen von Duplikaten ändern könnte. Wie im letzten Beispiel des Abschnitts 3.1.1 zu sehen ist, wurde daher für ATSQL2 die Möglichkeit vorgesehen, eine zeitliche Verschmelzung explizit vorzunehmen. Der zugehörige Verschmelzungsoperator der temporalen Relationenalgebra ist am Ende dieses Abschnitts beschrieben. Die Differenzbildung besitzt eine etwas umfangreichere Definition, sie wird daher in Abbildung 3.2 veranschaulicht. Es werden stets nur Tupel mit den expliziten Attributen t betrachtet, d. h. ausschließlich wertgleiche Tupel werden an der Differenzbildung beteiligt. In der ersten Zeile der Definition wird das Tupel ht k V T1 i der Relation r1 gewissermaßen als Ausgangsintervall benutzt, zu dem in den folgenden vier Zeilen wertgleiche Tupel der Relation r2 mit maximal überlappenden Zeitstempeln gesucht werden. Dabei werden in der zweiten Zeile der Anfangspunkt und in der dritten Zeile der Endpunkt solcher Zeitstempel festgelegt. Die vierte Zeile stellt sicher, daß bei der punktweisen Betrachtung nur sinnvolle Intervalle berücksichtigt werden. In der letzten Zeile wird auf Maximalität der Zeitstempel geprüft, d. h. Zeitstempel von Tupeln aus r2 dürfen keinen Zeitstempel eines wertgleichen Ergebnistupels überlappen. Alle in Definition 9 dargestellten Operationen genügen der Schnappschuß-Reduzierbarkeit. Ein Beweis dafür findet sich in [BJ96]. Dort wird jedoch, wie auch in [SBJ96a], eine andere Variante des Kreuzproduktes verwendet, die ursprünglich nicht schnappschuß-reduzierbar ist. Es werden dabei jeweils die Zeitstempel der zwei konkatenierten Tupel als explizite Attribute in das neue Tupel übergeben. Das heißt r1 ×vt r2 = {hht1 , V T1 i ◦ ht2 , V T2 i k V T i | (. . .)}, wobei (. . .) der Definition 9 entspricht und ht1 , V T1 i ein nicht-temporales Tupel darstellt, in dem der alte Zeitstempel V T1 nun explizit vorhanden ist. Dadurch ist es möglich, auch nach der Bildung eines temporalen Kreuzproduktes oder temporalen Verbundes, noch auf die ursprünglichen Zeitstempel aus den beteiligten Relationen zuzugreifen. Genau diese Existenz der Zeitstempel widerspricht jedoch der Schnappschuß-Reduzierbarkeit auf das nicht-temporale Kreuzprodukt, da das Produkt zweier Schnappschüsse keine Zeitstempel mehr enthält (vgl. Definition 6). Damit wird das Prinzip der sequentiellen Anfrageauswertung, bei der kein expliziter Zugriff auf temporale Informationen durch den Benutzer erwünscht ist, verletzt. Wenn der Bedarf nach einem solchen expliziten Zugriff besteht, ist dieses durch die Formulierung einer nicht-sequentiellen Anfrage vorgesehen. Daher soll hier der ursprünglichen Definition des Kreuzproduktes aus [BJS95] gefolgt werden, die auch in [Ste98a] verwendet wird. Eine weitere Operation der temporalen Relationenalgebra findet man in Definition 10. Die 3.2. DIE NEUE TEMPORALE ERWEITERUNG SQLTE r coal(r) 27 VT1 VT2 VT Abbildung 3.3: Verschmelzungs-Operator der temporalen Relationenalgebra (nach [BJ96]) Verschmelzungsoperation (coalescing operation) besitzt kein nicht-temporales relationales Gegenstück. Sie zerstört die ursprünglichen Zeitstempel und faßt Tupel zusammen, um eine Darstellung mit maximalen Intervallen für jeweils wertgleiche Tupel zu erreichen. Ein Anwendungsbeispiel ist zum Abschluß des Abschnitts 3.1.1 beschrieben. Bei der Erläuterung des temporalen Kreuzproduktes (s. o.) wurde auf die Gründe hingewiesen, weshalb die zeitliche Verschmelzung bei ATSQL2 nicht als implizite Operation vorgesehen ist. Definition 10 (Verschmelzungsoperator der temporalen Relationenalgebra) [BJ96] coalvt (r) = {ht k V T i | ∃V T1 , V T2 (ht k V T1 i ∈ r ∧ ht k V T2 i ∈ r ∧ V T1− < V T2+ ∧ V T − = V T1− ∧ V T + = V T2+ ∧ ∀V T3 (ht k V T3 i ∈ r ∧ V T − < V T3− < V T + ⇒ ∃V T4 (ht k V T4 i ∈ r ∧ V T4− < V T3− ≤ V T4+ )) ∧ 6 ∃V T5 (ht k V T5 i ∈ r ∧ (V T5− < V T − ≤ V T5+ ∨ V T5− ≤ V T + < V T5+ )))} Nachfolgend soll nun abschließend mit Hilfe der Abbildung 3.3 die Funktionsweise der Verschmelzungsoperation erläutert werden. Wie bei der temporalen Differenz werden stets nur Tupel mit den expliziten Attributen t betrachtet, d. h. nur jeweils wertgleiche Tupel werden an der Verschmelzung beteiligt. Die beiden Zeitstempel V T1 und V T2 aus der ersten Zeile der Definition bestimmen den Anfangs- und Endpunkt des verschmolzenen Ergebnistupels. Die beiden Punkte werden in der zweiten Zeile spezifiziert. Die dritte und vierte Zeile sorgen dafür, daß keine Lücken zwischen Anfangs- und Endpunkt existieren: Dieses geschieht, indem für jedes Intervall V T3 mit Anfangspunkt innerhalb des neuen verschmolzenen Intervalls V T ein weiteres Intervall V T4 gefordert wird, welches diesen Anfangspunkt überdeckt. Schließlich wird in der letzten Zeile sichergestellt, daß die erzeugten Ergebnistupel maximale Zeitstempel besitzen. Es dürfen somit keine Intervalle existieren, die Anfangs- oder Endpunkt des verschmolzenen Intervalls überlappen. 3.2 Die neue temporale Erweiterung SQLTE Nach der Vorstellung der Anfragesprache ATSQL2 im vorangehenden Abschnitt soll an dieser Stelle die Formulierung einer temporalen Erweiterung von SQL mit etwas anderen Schwerpunkten vorgenommen werden. Sehr gelungen erscheint bei ATSQL2 die Aufteilung der Sprache in die vier beschriebenen Ebenen von Spracherweiterungen und insbesondere die Prinzipien der temporalen Aufwärtskompatibilität und der Schnappschuß-Reduzierbarkeit. Dadurch wird eine leichte Formulierbarkeit 28 KAPITEL 3. TEMPORALE ERWEITERUNGEN VON SQL und Erlernbarkeit der Sprache ermöglicht. Es wird für den Anwender mit relativ einfachen Mitteln ein beträchtliches Maß an temporaler Funktionalität zur Verfügung gestellt. Es sind jedoch auch einige Punkte deutlich geworden, die im Rahmen der Neuentwicklung überdacht werden sollen. Dazu gehört vor allem das Problem der vertikalen temporalen Anomalie (s. Abschnitt 2.2.2), bedingt durch die Verwendung von Intervall-Zeitstempeln auf Tupeln. Dieses tritt im Beispiel zu den sequentiellen Erweiterungen (s. Abschnitt 3.1.1) zutage: Dort erscheint der Angestellte Meier doppelt, weil sein Chef während der interessanten Zeit eine Gehaltserhöhung erhalten hat. Zwar kann man diesen Effekt durch eine explizit ausgeführte zeitliche Verschmelzungsoperation vermeiden, jedoch erscheint ein Sprachkonstrukt sinnvoll, das die vertikale Anomalie an dieser Stelle vermeidet, ohne die Schnappschuß-Reduzierbarkeit durch eine automatische Coalesce-Operation zu verletzen (s. a. Abschnitt 3.1.2). Das abschließende Beispiel im Abschnitt 3.1.1 zeigt ein ähnliches Problem in der Ebene der nicht-sequentiellen Erweiterungen. Ebenfalls durch die Verteilung der Informationen über den Angestellten Boss auf mehrere Tupel ist der Anwender gezwungen, eine explizite Verschmelzung und einen Verbund in seine Anfrage einzubauen. Es findet keine Abstraktion zwischen der physischen Speicherung in den zugrundeliegenden nicht-temporalen Tabellen und der nichtsequentiellen Semantik statt, der Benutzer muß die Speicherstruktur bei der Anfrageformulierung unmittelbar berücksichtigen. Im folgenden soll daher speziell auf diese veränderungswürdig erscheinenden Punkte eingegangen werden, wenn die Konzeption der neuen temporalen Erweiterung von SQL festgelegt wird. Dabei werden zunächst grundlegende Konzepte beschrieben, um diese dann für die einzelnen Teile der Spracherweiterung zu konkretisieren. Die Syntax der Erweiterung orientiert sich so weit wie möglich an ATSQL2, eine abschließende formale Definition der gültigen Sprachausdrücke findet der Leser in Anhang A. Zum Abschluß dieses Kapitels werden verschiedene Möglichkeiten zur Präsentation der temporalen Informationen diskutiert. 3.2.1 Grundlegende Konzepte Durch das konzeptionelle Modell wird der gedankliche Entwurf der Spracherweiterung festgelegt in dem Sinne, daß für den Benutzer klar wird, wie er sich die Ausführung bestimmter Operationen auf den Daten vorzustellen hat. Für nicht-temporale relationale Datenbanken ist das konzeptionelle Modell hinlänglich bekannt: Der Benutzer stellt sich die Daten in Form von Relationen, Attributen und Tupeln vor und hat z. B. bei der Bildung des kartesischen Produktes zweier Relationen R1 und R2 die Kombination jedes einzelnen Tupels aus R1 mit allen Tupeln aus R2 vor Augen. Dieses Gedankenmodell von den Operationen der Sprache und den Objekten der Datenbank ist Voraussetzung für die erfolgreiche Formulierung von Datenbankanfragen. So wird eine Formulierung von Anfragen in SQL ohne die Vorstellungen des Benutzers aus dem obigen Beispiel kaum Erfolg haben. Sicher ist auch die Einfachheit des konzeptionellen Modells relationaler Datenbanken für deren große Verbreitung verantwortlich: Der Benutzer wird nach Kenntnisnahme weniger Datenobjekte und Operationen Anwender einer mächtigen Datenbanksprache, ohne sich zunächst um die Details von physischer Speicherung und Optimierung kümmern zu müssen. Diese Einfachheit gilt es nach Möglichkeit zu erhalten, wenn SQL um temporale Funktionalität erweitert wird. Diesem Anspruch wird das konzeptionelle Modell von ATSQL2 auf den ersten beiden Ebenen der Spracherweiterung zweifellos gerecht: Die aufwärtskompatiblen wie auch die temporal 3.2. DIE NEUE TEMPORALE ERWEITERUNG SQLTE 29 AngNr Name Gehalt VT 12 13 27 27 Müller Meier Boss Boss 5000 6000 5900 6400 [05.10.99, ∞) [05.10.99, ∞) [05.10.99, 12.10.99) [12.10.99, ∞) AngNr VT Name VT Gehalt VT 12 13 27 [05.10.99, ∞) [05.10.99, ∞) [05.10.99, ∞) Müller Meier Boss [05.10.99, ∞) [05.10.99, ∞) [05.10.99, ∞) 5000 6000 5900 6400 [05.10.99, ∞) [05.10.99, ∞) [05.10.99, 12.10.99) [12.10.99, ∞) Tabelle 3.1: Die Datenmodelle von ATSQL2 und SQLTE am Beispiel aufwärtskompatiblen Sprachausdrücke stimmen mit den Operationen von SQL überein. Im zweiten Falle werden lediglich die Daten durch Schnappschußbildung aus der temporalen Datenbank gewonnen, was jedoch für den Benutzer zunächst verborgen bleibt. In beiden Fällen ist für den Benutzer das nicht-temporale Gedankenmodell ausreichend, welches ihm durch den Einsatz von SQL vertraut ist. Diese ersten beiden Ebenen der Spracherweiterung sollen daher auch bei SQLTE Verwendung finden. Durch die Aufwärtskompatibilität und die temporale Aufwärtskompatibilität sowie das zugrundeliegende relationale Datenmodell ist die Semantik in diesem Bereich festgelegt. Weiterhin sollen auch die dritte und vierte Ebene von Spracherweiterungen sowie die dort enthaltene Schnappschuß-Reduzierbarkeit und die darüber hinaus vorgesehene temporale Funktionalität durch explizite Verwendung der Zeitstempel für SQLTE genutzt werden. Um dabei dem genannten Problem der vertikalen temporalen Anomalie zu begegnen, sollen Zeitstempel auf Attributen in Form von temporalen Elementen verwendet werden (s. Abschnitt 2.2.1 und 2.2.2). Auf diese Weise wird es, ebenso wie im nicht-temporalen relationalen Modell, möglich, alle Informationen eines Objektes der realen Welt (real world object) in einem Tupel der zugehörigen Relation abzulegen. Durch die Zeitstempel enthält ein temporales Tupel so die gesamte Historie eines Objektes der realen Welt. In Tabelle 3.1 sind zum Vergleich die Beispielinformationen aus Abschnitt 3.1.1 im Datenmodell von ATSQL2 und von SQLTE dargestellt. In der letztgenannten Variante findet keine Aufteilung der verschiedenen temporalen Versionen der Informationen über mehrere Tupel statt. Eine inhärente temporale Verschmelzung findet nur innerhalb der komplexen Tupel statt, d. h. es werden nur dann wertgleiche Informationen verschmolzen, wenn sich diese auf dasselbe Objekt der realen Welt beziehen. Auf diese Weise vermeidet man auf der einen Seite zunächst die Notwendigkeit einer expliziten Verschmelzungsoperation, da bereits alle sinnvollen temporalen Verschmelzungen automatisch vorgenommen werden: Ein Zusammenfügen von wertgleichen Informationen aus verschiedenen komplexen Tupeln ist keine Frage von temporaler Verschmelzung mehr, sondern vielmehr eine der Duplikatelimination nach Art des bekannten Operators distinct in SQL. Andererseits gerät man im Bereich der sequentiellen Erweiterungen nicht mit der Schnappschuß-Reduzierbarkeit in Konflikt (vgl. Abschnitt 3.1.2), wenn man innerhalb eines komplexen Tupels keine zeitlichen Überlappungen der einzelnen Attributeinträge zuläßt. Legt man eine einzelne lineare Zeitlinie 30 KAPITEL 3. TEMPORALE ERWEITERUNGEN VON SQL zugrunde, erscheint eine solche Festlegung überaus sinnvoll — in der Tabelle 3.1 könnte der Angestellte Boss beispielsweise nicht gleichzeitig zwei unterschiedliche Gehälter beziehen. Ist die Darstellung einer solchen Information — etwa ein Mitarbeiter, der zwei Abteilungen zugleich angehört — gewünscht, so ist dieses eine Erweiterung des Datenbankschemas und hat nichts mit temporaler Erweiterung zu tun. Das skizzierte gedankliche Modell der komplexen Tupel wird sowohl für die Ebene der sequentiellen wie auch für die der nicht-sequentiellen Erweiterungen gelten. Für diese beiden Ebenen soll die neue Sprache nachfolgend beschrieben werden, insbesondere wird dort auf die genannte Problematik in den Beispielen aus Abschnitt 3.1.1 eingegangen. 3.2.2 Temporale Anfrageformulierung Abbildung 3.4 zeigt die Syntaxübersicht für die Anfrageformulierung in SQLTE. Weitergehende Details sind in Anhang A zu finden. Wie bereits von ATSQL2 aus Abschnitt 3.1 gewohnt, beginnen temporale Anfragen durch die Schlüsselwörter validtime und nonsequenced validtime, wenn sequentielle bzw. nicht-sequentielle Funktionalität gewünscht wird. Für beide Varianten kann der Anwender ein Zeitintervall für die Befehlsausführung festlegen. Unterbleibt diese Festlegung, so wird stets das Intervall [now, forever) verwendet, welches vom Zeitpunkt der Befehlsanwendung1 bis zum Ende der Zeitdarstellung im System reicht. Zusammen mit den genannten Schlüsselwörtern bildet dieses explizit angegebene Intervall ein timeflag, welches Anfragen vorangestellt werden kann (vgl. Abbildung 3.4). Für die Mengenoperationen union, except und intersect bedarf es für jede einzelne zugehörige Teilanfrage des Timeflags, da auch die Kombination von Anfragen unterschiedlicher temporaler Erweiterungsebenen möglich sind. Es gilt jedoch ebenso wie im nicht-temporalen SQL die Maßgabe, daß Mengenoperationen nur bei kompatiblen Projektionslisten zulässig sind. So ist im folgenden Beispiel die erste Anfrage erlaubt, während die zweite unzulässig ist. Dieses begründet sich damit, daß sowohl sequentielle wie nicht-sequentielle Anfragen bei einfachen Projektionslisten ein temporales Ergebnis mit komplexen Tupeln liefern. Die erste Anfrage vereinigt also typgleiche temporale Tupel, während bei der zweiten Anfrage der Versuch unternommen wird, temporale Attribute vom Typ number und varchar zu mischen. (nonsequenced validtime select Name from Ang a where validtime(a) overlaps ’07.07.1977’) union (validtime select Name from Ang); (nonsequenced validtime select Nr from Ang a) union (validtime select Name from Ang); Gleiches gilt für die Verwendung von Subqueries in from- oder where-Klauseln: Durch das Timeflag können Teilanfragen unterschiedlicher temporaler Erweiterungsebenen kombiniert wer1 Im Gegensatz zu dem dynamischen Zeitpunkt now, wie in Abschnitt 2.2.1 beschrieben, ist hier der feste Zeitpunkt der Befehlsanwendung gemeint. Zur Unterscheidung ist in der Literatur dafür teilweise auch der Begriff current zu finden. Diese Unterscheidung erscheint hier jedoch nicht notwendig, da für SQLTE nur das statische now zu Anwendung kommt. Weiterhin ist nicht mit Verwechslungen zu rechnen, da das dynamische now i. a. nur als Endpunkt eines Intervalls als Alternative zu forever verwendet wird, nicht jedoch als Anfangspunkt (vgl. Abschnitt 2.2.1). 3.2. DIE NEUE TEMPORALE ERWEITERUNG SQLTE statement := query := query timeflag dml 31 timeflag sfw ; ) ddl ) query UNION MINUS INTERSECT timeflag := PERIOD NONSEQUENCED [ timestamp , timestamp ) VALIDTIME timeflag sfw := SELECT * ) FROM sfw ) alias-id table-id scalarexp ... , , ... WHERE condexp Abbildung 3.4: Syntax der SQLTE-Befehle zur Anfrageformulierung den, jedoch müssen die beteiligten Typen kompatibel sein. Dabei werden nicht-temporale Typen (z. B. select Name from Ang) und temporale Typen (z. B. validtime select Name from Ang) unterschieden. Das Ergebnis der folgenden Anfrage ist ebenfalls nicht-temporal und zwar vom Datentyp date. nonsequenced validtime select begin(validtime(a)) from Ang a; Für den Rückgabewert des validtime-Operators innerhalb der Projektionsliste wäre die Schaffung eines neuen nicht-temporalen Datentyps timestamp denkbar, so daß Anfrageergebnisse wie das der nachfolgenden Anweisung für Subqueries oder temporale Berechnungen verwendet werden könnten. nonsequenced validtime select validtime(a) from Ang a; Diese Variante soll jedoch zunächst nicht als Bestandteil der Sprache formuliert werden. Ebenso wie für Kombinationen von temporalen Attributen und Zeitstempeln wie in der Anfrage nonsequenced validtime select a.name, validtime(a) from ang a bleibt hier die Möglichkeit, die Anfragesprache in Zukunft entsprechend zu erweitern. Sequentielle Anfragen Bei sequentiellen Anfragen findet eine inhärente temporale Interpretation der Daten gemäß der temporalen Semantik aus Abschnitt 3.1.2 statt, jedoch auf Basis der im vorangehenden Abschnitt 32 KAPITEL 3. TEMPORALE ERWEITERUNGEN VON SQL beschriebenen komplexen Tupel. Das problematische Beispiel zu den sequentiellen Erweiterungen aus Abschnitt 3.1.1 stellt sich unter SQLTE wie folgt dar. create table Ang (AngNr number, Name varchar(30), Gehalt number, ChefNr number) as validtime; validtime period [’05.10.1999’, forever) insert into Ang values (12, ’Müller’, 5000, 27); validtime period [’05.10.1999’, forever) insert into Ang values (13, ’Meier’, 6000, 27); validtime period [’05.10.1999’, forever) insert into Ang values (27, ’Boss’, 5900, 27); validtime period [’12.10.1999’, forever) update Ang set Gehalt = 6400 where AngNr = 27); validtime select a1.AngNr, a1.Name, a1.Gehalt, a2.Name from Ang a1, Ang a2 where a1.ChefNr = a2.AngNr and a1.Gehalt > 5900; =⇒ AngNr Name Gehalt a2.Name VT 13 Meier 6000 Boss [05.10.1999, ∞) 27 Boss 6400 Boss [12.10.1999, ∞) Es erfolgt die gewünschte Ausgabe der Angestellen für die Zeit, während der ein Gehalt von über 5900 ausgezahlt wurde. Da in der Ausgabe nur zwei verschiedene Zeitstempel vorkommen, erfolgt die Präsentation mit Hilfe von Zeitstempeln auf Tupeln (vgl. dazu Abschnitt 3.2.5). Die Anfrageverarbeitung ist hier im Gegensatz zu dem entsprechenden Beispiel aus Abschnitt 3.1.1 unproblematisch, da an der Bildung des kartesischen Produktes nur drei (komplexe) Tupel beteiligt sind. Die resultierenden neun Paare werden durch die erste Selektionsbedingung a1.ChefNr = a2.AngNr eingeschränkt auf die Kombinationen Müller/Boss, Meier/Boss und Boss/Boss. Die zweite Selektionsbedingung a1.Gehalt > 5900 läßt nur die Tupel Meier/Boss und Boss/Boss zu, wobei die letzte Kombination auf die Gültigkeitszeit des Selektionsbedingung eingeschränkt wird, nämlich [12.10.1999, ∞). Nicht-sequentielle Anfragen Bei nicht-sequentiellen Anfragen findet keine inhärente temporale Interpretation der Daten statt. Stattdessen stehen dem Benutzer die expliziten Zeitstempel von Tupeln (z. B. validtime(a)) oder ganzer temporaler Subqueries (z. B. validtime(nonsequenced validtime select Name from Ang)) zur Verfügung. Die letztgenannte Anwendung von validtime liefert die Vereinigung der Zeitstempel aller beteiligten komplexen Tupel, falls das Ergebnis der Subquery mehrere Tupel umfaßt. Weiterhin sind die Beziehungen aus Tabelle 2.5 (precedes, overlaps, meets, contains, equals sowie begin und end) als Operatoren auf Zeitstempeln anwendbar. Das problematische Beispiel zu den nicht-sequentiellen Erweiterungen aus Abschnitt 3.1.1 stellt sich damit unter SQLTE wie folgt dar. Gefragt wurde dort nach allen Mitarbeitern, die seit dem 05.10.1999 ununterbrochen im Unternehmen tätig waren und es noch immer sind. Die gesamte Historie der gesuchten Mitarbeiter erhält man mit der folgenden Anfrage, während die zweite Variante als TUC-Anfrage nur die aktuellen Informationen liefert, wie dieses in Abschnitt 3.2. DIE NEUE TEMPORALE ERWEITERUNG SQLTE 33 3.1.1 gewünscht war. In beiden Fällen ist keine explizite Verschmelzungsoperation wie unter ATSQL2 vonnöten. Die Ausgabe erfolgt wiederum mit Hilfe von Zeitstempeln auf Tupeln (vgl. dazu Abschnitt 3.2.5). Da der Ausdruck validtime(a) im Tupel des Angestellten Nr. 27 zum Zeitstempel [05.10.1999, ∞) ausgewertet wird, entspricht auch dieses Tupel der Auswahlbedingung. nonsequenced validtime select * from Ang a where validtime(a) contains period [’05.10.1999’, now); =⇒ AngNr Name Gehalt ChefNr VT 12 Müller 5000 27 [05.10.1999, ∞) 13 Meier 6000 27 [05.10.1999, ∞) 27 27 Boss Boss 5900 6400 27 27 [05.10.1999, 12.10.1999) [12.10.1999, ∞) select * from ( nonsequenced validtime select * from Ang a where validtime(a) contains period [’05.10.1999’, now)); =⇒ AngNr Name Gehalt ChefNr 12 13 27 Müller Meier Boss 5000 6000 6400 27 27 27 Abschließend sei ein weiteres Beispiel beschrieben, das auch nicht-temporale Auswahlbedingungen sowie Verbundbildung berücksichtigt. Die Ausgangslage sei die folgende AngestellenTabelle. Es soll die Frage beantwortet werden, welche Personen von Anfang 1988 bis Ende 1989 in der Einkaufsabteilung gearbeitet haben und während des Jahres 1999 in der Verkaufsabteilung tätig waren. Nr Name Gehalt Abteilung 23 | [88, ∞) Müller | [88, ∞) 4000 | [88, 89) 4500 | [89, ∞) Einkauf | [88, 95) Verkauf | [95, ∞) nonsequenced validtime select a1.Name from Ang a1, (validtime select * from Ang s1 where s1.Abt = ’Einkauf’ and s1.Nr = a1.Nr) a2, (validtime select * from Ang s2 where s2.Abt = ’Verkauf’ and s2.Nr = a1.Nr) a3 where validtime(a2) contains period [’01.01.1988’, ’01.01.1990’) and validtime(a3) contains period [’01.01.1999’, ’01.01.2000’); =⇒ a1.Name VT Müller [01.01.1988, ∞) Die beiden Unteranfragen zu a2 und a3 schränken die Tupel auf die Verkaufs- bzw. Einkaufsabteilung ein. Da es sich um sequentielle Anfragen handelt, erfolgt diese Einschränkung jeweils für das gesamte Tupel, d. h. validtime(a2) und validtime(a3) liefern genau die Gültigkeitszeiten, währenddessen die Bedingungen s1.Abt = ’Einkauf’ bzw. s2.Abt = ’Verkauf’ erfüllt sind. 34 KAPITEL 3. TEMPORALE ERWEITERUNGEN VON SQL Für diese Anfrage ist die Kombination von sequentiellen und nicht-sequentiellen Konstrukten notwendig, wie nachfolgend erläutert werden soll. Mit einem einzigen nicht-sequentiellen Kommando ließe sich diese Anfrage nicht formulieren, da mit einer Auswahlbedingung wie s1.Abt = ’Einkauf’ nur ein komplexes Tupel ausgewählt wird, welches diese Bedingung erfüllt, jedoch keine Einschränkung stattfindet. Somit würden alle Mitarbeiter gefunden, die jemals in der Einkaufsabteilung gearbeitet haben. Andererseits liefert eine rein sequentielle Anfrage auch nicht das gewünschte Ergebnis, selbst wenn man mit Hilfe des Timeflags die Teilanfragen auf period [’01.01.1988’, ’01.01.1990’) bzw. auf period [’01.01.1999’, ’01.01.2000’) einschränken würde. Das Resultat wären in diesem Fall Mitarbeiter, die während der genannten Zeiten irgendwann in den entsprechenden Abteilungen tätig waren, jedoch nicht notwendigerweise die Angestellten, die während der gesamten Zeit der Abteilung angehörten. Alternativ läßt sich dieselbe Anfrage wie auch folgt stellen, dabei wird statt der Verbundbildung die Fähigkeit des validtime-Operators genutzt, komplette Subqueries zu verarbeiten. nonsequenced validtime select a1.Name from Ang a1 where validtime( validtime select * from Ang s1 where s1.Abt = Einkauf’ and s1.Nr = a1.Nr) contains period [’01.01.1988’, ’01.01.1990’) and validtime( validtime select * from Ang s2 where s2.Abt = Verkauf’ and s2.Nr = a1.Nr) contains period [’01.01.1999’, ’01.01.2000’); Nachfolgend ist die gleichbedeutende ATSQL2-Anfrage dargestellt. Dort sind jeweils Verschmelzungsoperationen notwendig, die durch (period) explizit angegeben werden. nonsequenced validtime select a1.Name from (validtime (select Nr, Name from Ang where Abteilung=’Einkauf’) (period)) a1, (validtime (select Nr, Name from Ang where Abteilung=’Verkauf’) (period)) a2 where a1.Nr = a2.Nr and validtime(a1) contains period [date ’1988’ - date ’1990’) and validtime(a2) contains period [date ’1999’ - date ’2000’); 3.2.3 Temporale Datenmanipulation Die in Kapitel 2.3 vorgestellten Prinzipien zur temporalen Erweiterung von Datenmodellen beschränken sich nicht auf Anfrageoperationen, wenngleich dort der Schwerpunkt vieler Veröffentlichungen und auch das Hauptaugenmerk dieser Arbeit liegt. Dennoch sollen zum Sprachumfang von SQLTE Funktionen zur Datenmanipulation (data manipulation language — DML) gehören, wenn auch nur in eingeschränktem Umfang. Dadurch wird eine vollständigere Testumgebung geschaffen, mit der es möglich ist, den tatsächlichen Bedürfnissen der nachträglichen Datenmanipulation ohne eine komplette Löschung und Neuanlage von Tabellen gerecht zu werden. In diesem Abschnitt wird daher beschrieben, welche Funktionalität von den einzelnen Kommandos zur Verfügung gestellt wird, wo deren Grenzen liegen und welche weiteren Möglichkeiten denkbar sind. In Abbildung 3.5 ist die grundsätzliche Syntax der Befehle zur Datenmanipulation zu sehen, für eine komplette und detaillierte Übersicht sei auf Anhang A verwiesen. Die Kommandos zur Datenmanipulation beginnen ebenso wie die temporalen Anfragen mit einem Timeflag, das die Art der temporalen Unterstützung signalisiert. Dieses Timeflag soll im Bereich der Datenmanipulation derzeit nur eingeschränkt unterstützt werden, präzise ist die 3.2. DIE NEUE TEMPORALE ERWEITERUNG SQLTE statement := 35 query timeflag ; dml ddl timeflag := NONSEQUENCED [ PERIOD , timestamp timestamp ) VALIDTIME , dml := ) INSERT INTO table-id VALUES WHERE DELETE FROM , ) column-id ) table-id ) condexp table-id WHERE UPDATE constant SET column-id = condexp scalarexp , Abbildung 3.5: Syntax der SQLTE-Befehle zur Datenmanipulation Verwendung von nonsequenced-Konstrukten nicht vorgesehen. Auf eine mögliche Funktionalität dieser Befehle wird zum Abschluß dieses Abschnitts hingewiesen. Zur Verfügung stehen somit die Anwendung eines leeren Timeflags auf temporale Tabellen, was zu einer temporal aufwärtskompatiblen Funktionalität (TUC) führt, sowie die Verwendung des Schlüsselwortes validtime, wodurch eine sequentielle Befehlsausführung (SEQ) erreicht wird. Wie im Falle der select-Anweisungen kann die jeweilige Funktion durch die Angabe von period explizit auf ein Zeitintervall eingeschränkt werden. Fehlt diese Spezifikation, so wird ebenso wie bei der Anfrageformulierung stets mit dem Intervall [now, forever) gearbeitet. Nachfolgend wird die Funktionsweise der einzelnen Datenmanipulationsbefehle in den beiden Bereichen der temporalen Aufwärtskompatibilität und der sequentiellen Anweisungen beschrieben. Auf Begründungen für die Einhaltung von temporaler Aufwärtskompatibilität bzw. Schnappschuß-Reduzierbarkeit wird verzichtet, da sich diese leicht anhand der Definitionen 5 und 6 nachvollziehen lassen. Die beiden insert-Kommandos erschaffen dabei stets neue komplexe Tupel. Eine temporale Änderung oder Ergänzung bestehender Tupel etwa durch ein insert bei Verwendung identischer Primärschlüssel ist nicht vorgesehen. Diese Vorgehensweise begründet sich auf die temporale Aufwärtskompabilität bzw. die Schnappschuß-Reduzierbarkeit: Ein nicht-temporales insert liefert stets ein neues Tupel, so daß TUC- wie auch SEQ-insert ebenso immer ein neues Tupel erzeugen müssen, damit die Anzahl der Tupel je Schnappschuß übereinstimmt. Für einen weitergehenden Sprachumfang, etwa um Zeitbereiche bestimmter Tupel zu ergänzen, sei auf den letzten Absatz in diesem Abschnitt verwiesen, wo auf die Möglichkeit von nicht-sequentiellen 36 KAPITEL 3. TEMPORALE ERWEITERUNGEN VON SQL angegebenes Intervall Zeit Zeitstempel Attribut A Zeitstempel Attribut B Zeitstempel Attribut C bildet Zeitstempel des alten Attributwerts: bildet Zeitstempel des neuen Attributwerts: Abbildung 3.6: Zeitstempel bei der sequentiellen Update-Operation Datenmanipulationsoperationen eingegangen wird. insert (TUC): Es wird stets ein neues komplexes Tupel geschaffen, mit einem Eintrag je Attribut und dem Zeitstempel [now, forever). Die Dateneingabe erfolgt durch Auflistung der einzufügenden Werte, die Eingabe über eine Subquery ist nicht vorgesehen. delete (TUC): Alle Zeitstempel der komplexen Tupel, die zum aktuellen Zeitpunkt now den Auswahlkriterien genügen oder den aktuellen Zeitpunkt nur überlappen, falls keine Auswahlkriterien in der where-Klausel angegeben wurden, werden sofort beendet. Es erfolgt also ein Schnitt zum Zeitpunkt now. Wird auf diese Weise der Zeitstempel eines Attributs zum leeren Intervall beschnitten, wird der Attributwert gelöscht. update (TUC): Alle Attribut-Einträge der komplexen Tupel, die zum aktuellen Zeitpunkt now den Auswahlkriterien genügen oder den aktuellen Zeitpunkt nur überlappen, falls keine Auswahlkriterien in der where-Klausel angegeben wurden, werden dupliziert und mit neuen Zeitstempeln versehen. Die Duplikate werden gemäß der set-Klausel verändert. Die alten Zeitstempel werden — wie beim delete — zum aktuellen Zeitpunkt beendet. Die neuen Zeitstempel ergeben sich aus genau dem Rest, der bei den alten entfernt wurde. Attribute mit leeren Zeitstempeln werden wiederum entfernt. insert (SEQ): Es wird stets ein neues komplexes Tupel geschaffen, mit einem Eintrag je Attribut und dem spezifizierten Zeitstempel oder [now, forever), falls keine explizite Angabe vorhanden. Die Dateneingabe erfolgt durch Auflistung der einzufügenden Werte, die Eingabe über eine Subquery ist nicht vorgesehen. delete (SEQ): Alle Zeitstempel der komplexen Tupel, die im Bereich des angegebenen Intervalls den Auswahlkriterien genügen oder das angegebene Intervall nur überlappen, falls keine Auswahlkriterien in der where-Klausel angegeben wurden, werden für die angegebene Zeit ausgeblendet. Es erfolgt also ein Entfernen der gewünschten Gültigkeitszeit aus den Zeitstempeln. Wird der Zeitstempel eines Attributs komplett überlappt, so daß ein leerer Zeitstempel entstehen würde, ist die Löschung dieses Attributwertes vorgesehen. update (SEQ): Alle Attribut-Einträge der komplexen Tupel, die im Bereich des angegebenen Intervalls den Auswahlkriterien genügen oder das angegebene Intervall nur überlappen, falls keine Auswahlkriterien in der where-Klausel angegeben wurden, werden dupliziert 3.2. DIE NEUE TEMPORALE ERWEITERUNG SQLTE 37 und mit neuen Zeitstempeln versehen (s. Abbildung 3.6). Die Duplikate werden gemäß der set-Klausel verändert. Die alten Zeitstempel werden — wie beim delete — für die angegebene Zeit ausgeblendet. Die neuen Zeitstempel ergeben sich aus genau dem Rest, der bei den alten entfernt wurde. Alte Attribute mit leeren Zeitstempeln (in Abbildung 3.6 das Attribut B) werden entfernt. Verzichtet man bei der SEQ-Variante von insert auf die explizite Angabe eines Intervalls im Timeflag, so ist die Funktionalität mit dem TUC-insert identisch. Dennoch sind beide Kommandos berechtigt, da sie durch eine unterschiedliche Syntax erreicht werden: Der Anwender des TUC-insert ist sich möglicherweise gar nicht bewußt, daß er mit einer temporalen Tabelle arbeitet. Bei update und delete beschränkt sich der Unterschied zwischen der TUC- und der SEQVariante ohne explizite Intervallangabe auf die Auswahl der beteiligten Tupel. Im Falle der TUC-Befehle sind nur Tupel beteiligt, die zum aktuellen Zeitpunkt den Auswahlbedingungen entsprechen, bei den sequentiellen Befehlen auch solche Tupel, deren Gültigkeitszeit in der Zukunft liegt, d. h. mit [now, forever) überlappt. So hat im Beispiel zu den sequentiellen Anfragen in Abschnitt 3.2.2 die sequentielle update-Operation denselben Effekt wie eine temporal aufwärtskompatible Änderungsoperation, die am 12.10.1999 ausgeführt wird. Abschließend sei bemerkt, daß mit den vorgestellten Operationen zur temporalen Datenmanipulation keine expliziten Änderungen an den Zeitstempeln möglich sind. Wie schon bei der Anfrageformulierung gesehen, bedarf es für den expliziten Zugriff auf die Zeitinformationen der nicht-sequentiellen Sprachkonstrukte. Man könnte sich also in diesem Rahmen eine Anweisung nonsequenced insert vorstellen, die es erlaubt, komplexe temporale Tupel mit expliziten Zeitangaben einzufügen. Auch wären sicher Befehle der Art nonsequenced update set begin(validtime(t)) = ’10.11.1999 16:18:34’ wünschenswert, um durch spezifische Änderungsoperationen z. B. Zeitbereiche zu ergänzen. Wie jedoch zu Beginn dieses Abschnitts erwähnt, sollen die Änderungsoperationen nicht schwerpunktmäßig betrachtet werden, so daß ein weiterführender Funktionsumfang hier nicht spezifiziert werden soll. 3.2.4 Temporale Datendefinition Im Bereich der temporalen Datendefinition verhält es sich ähnlich wie im vorangehenden Abschnitt zur temporalen Datenmanipulation: Es soll zunächst nur soviel Funktionalität vorgesehen werden, wie für eine sinnvolle Testumgebung benötigt wird. Das bedeutet konkret, daß nur die beiden Befehle create table und drop table vorgesehen werden sollen und zwar in der Form, wie im Syntaxdiagramm in Abbildung 3.7 dargestellt. Für eine komplette und detaillierte Übersicht sei wiederum auf Anhang A verwiesen. Für die Datendefinition wird nicht das von Anfrageformulierung und Datenmanipulation bekannte Timeflag benötigt, da keine Differenzierung bezüglich der Sequentialisierbarkeit vorgenommen wird. Stattdessen wird bei create table nur zwischen nicht-temporalen und temporalen Tabellen unterschieden, die letztgenannten werden durch das Nachstellen der Schlüsselwörter as validtime erzeugt. Für das Entfernen von Tabellen mittels drop table ist der Grad der temporalen Unterstützung unerheblich, daher fehlt hier eine enstprechende Angabe. Wie weiterhin in der Abbildung 3.7 zu sehen ist, bleibt der Bereich der (temporalen) Integritätsbedingungen leider komplett unberücksichtigt. Die Anlage von Sichten, Indexen, Sequenzen und andererer SQL-Features sowie ein jeweils zugehöriges drop- und alter-Kommando 38 KAPITEL 3. TEMPORALE ERWEITERUNGEN VON SQL ddl := CREATE TABLE table-id table-id ( column-id datatype ( DROP TABLE AS VALIDTIME , Abbildung 3.7: Syntax der SQLTE-Befehle zur Datendefinition Nr Name Gehalt Abteilung 12 | [84, ∞) Rastlos | [84, 90) ∪ [93, ∞) Rastlos-Meier | [90, 93) 4000 | [84, 91) 4500 | [91, 96) 5000 | [96, ∞) Einkauf | [84, 87) Verkauf | [88, 89) ∪ [92, ∞) Produktion | [87, 88) ∪ [89, 92) Stetsgleich | [80, ∞) 4800 | [80, ∞) Personalbüro | [80, ∞) 13 | [80, ∞) Tabelle 3.2: Beispiel der Darstellung mit Attribut-Zeitstempeln und temporalen Elementen fehlen ebenfalls. Diese Eingrenzungen waren notwendig, um den Umfang der Arbeit überschaubar zu halten. 3.2.5 Präsentation temporaler Anfrageergebnisse Das Präsentationsmodell legt die Art und Weise der Darstellung von Informationen aus dem temporalen DBMS fest. Eine Ausgabe von Informationen erfolgt im wesentlichen im Rahmen der Anfragebearbeitung, so daß zur Festlegung des Präsentationsmodells nur Anfrageoperationen betrachtet werden. Die Darstellung der Ergebnisse von aufwärtskompatiblen und temporal aufwärtskompatiblen Anfragen erfolgt sinnvoller Weise ebenso wie im unterliegenden DBMS. Das heißt, die nichttemporalen Relationen werden wie üblich tabellenförmig ausgegeben, mit einer Zeile je Tupel und einer Spalte je Attribut. Für die sequentiellen und nicht-sequentiellen Spracherweiterungen bedarf es nun einer zweckmäßigen Erweiterung dieser Darstellung, so daß zusätzlich temporale Informationen präsentiert werden können. Hier ist eine möglichst übersichtliche Darstellung Zielsetzung, also eine Vermeidung der temporalen Anomalien durch die zusammenhängende Ausgabe der gesamten Geschichte eines Objektes der realen Welt. Bedingung ist dabei nur eine tabellarische Darstellung in Anlehnung an die Präsentation nicht-temporaler Relationen. Wie in Abschnitt 2.2.2 gesehen, ermöglicht die Benutzung von Zeitstempeln auf Attributen durch temporale Elemente eine solche Darstellung. Diese Art der Ausgabe würde auch der Konzeption der komplexen Tupel aus Abschnitt 3.2.1 entsprechen. Problematisch ist dabei jedoch die unbefriedigende Lesbarkeit durch den Benutzer, wenn einzelne Tupel in ihrer Geschichte viele Änderungen erfahren haben. Besonders in Kombination mit zahlreichen Attributen wird eine solche Darstellung unübersichtlich. Dieses wird im Beispiel in Tabelle 3.2 angedeutet. Im Grunde benötigte man für die zusammenhängende Darstellung neben Zeilen und Spalten eine dritte Dimension für die Zeit. Eine Präsentation in Anlehnung an die einfache und gewohn- 3.2. DIE NEUE TEMPORALE ERWEITERUNG SQLTE Nr 39 Name Gehalt Abteilung VT 12 12 12 12 12 12 12 12 Rastlos Rastlos Rastlos Rastlos-Meier Rastlos-Meier Rastlos-Meier Rastlos Rastlos 4000 4000 4000 4000 4500 4500 4500 5000 Einkauf Produktion Verkauf Produktion Produktion Verkauf Verkauf Verkauf [84, 87) [87, 88) ∪ [89, 90) [88, 89) [90, 91) [91, 92) [92, 93) [93, 96) [96, ∞) 13 Stetsgleich 4800 Personalbüro [80, ∞) Tabelle 3.3: Beispiel der Darstellung mit Tupel-Zeitstempeln und temporalen Elementen te zweidimensionale Tabellenstruktur von SQL erscheint daher mit Zeitstempeln auf Attributen nicht realisierbar. Daher könnte sich die Verwendung von temporalen Elementen mit Zeitstempeln auf Tupeln als ein sinnvoller Mittelweg zwischen den Anforderungen der zusammenhängenden Historie und einer guten Lesbarkeit erweisen. Die dabei vorhandenen Redundanzen könnten angesichts der erhöhten Übersichtlichkeit in Kauf genommen werden, zumal diese nur in der Darstellung erscheinen und — abgesehen von Platz auf dem Bildschirm — keine Ressourcen belegten. Weiterhin erscheint neben der Wiederholung der unveränderten Attribute bei Änderung eines einzelnen Attributwertes eine zeitlich aufsteigende Sortierung der Tupel sinnvoll, zumal sonst in der Ebene der sequentiellen Erweiterungen keine Sortierung nach den impliziten Zeitstempeln möglich ist. Für diese zeitliche Sortierung wäre die Ordnung nach den Anfangspunkten der temporalen Intervalle denkbar. Da ein komplexes Tupel in der Regel mehrere Zeilen beanspruchen wird, würde ein größerer Abstand zwischen Zeilen verschiedener Tupel die Lesbarkeit erhöhen. Schließlich erhält man eine Darstellung wie im Beispiel in Tabelle 3.3. Insgesamt kann man feststellen, daß sich keine der beiden beschriebenen Varianten als die eindeutig bessere herausstellt: Die erste entspricht der Konzeption der Sprache und bietet wegen der stets vorhandenen zeitlichen Verschmelzung einen Überblick der einzelnen Attributwerte, während die zweite eine gute Übersicht für den zeitlichen Verlauf des gesamten Tupels bietet, diesen jedoch durch Auftreten der vertikalen temporalen Anomalie und der damit verbundenen Redundanz erkauft. Bei der Implementierung der Sprache könnte man daher evtl. beide Varianten vorsehen und die Art der aktuellen Präsentation z. B. mit Hilfe eines Schalters in der Benutzeroberfläche realisieren. Kapitel 4 Planung und Entwurf der Implementation In den vorangegangenen beiden Kapiteln wurden Grundlagen zu temporalen Spracherweiterungen erläutert, mit ATSQL2 eine prototypisch verfügbare temporale Anfragesprache vorgestellt und schließlich auf dieser Basis die temporale Erweiterung SQLTE nach eigenen Schwerpunkten entwickelt. In diesem Kapitel sollen nun diese Vorbereitungen genutzt werden, um die Vorgehensweise zur Implementation von SQLTE in ihren Grundzügen festzulegen. Ein Ziel der vorliegenden Arbeit ist es, bei der Implementation objekt-relationale Konzepte zu berücksichtigen. Es werden daher zunächst die neuen objekt-orientierten Features von Oracle8 vorgestellt. Weiterhin werden auf der Grundlage dieser Möglichkeiten zwei Varianten für das Speichermodell von SQLTE entwickelt. Abschließend werden Überlegungen zur Architektur des Programms angestellt und ein erster Ansatz zur Modularisierung formuliert. 4.1 Objekt-relationale Möglichkeiten Ein Ziel der Implementation der in den vorangehenden Kapiteln entwickelten temporalen Erweiterung von SQL ist der Einsatz der unter Oracle8 erstmalig verfügbaren objekt-relationalen Features. Diese werden durch eine Ergänzung zum Datenbanksystem zur Verfügung gestellt, der sogenannten Objects Option. Auf diese Weise wird das bisher zugrundeliegende relationale Modell zum objekt-relationalen Modell erweitert, das als wesentliche Neuerung benutzerdefinierte Objekttypen unterstützt, welche die abstrahierte Darstellung komplexer Entities der realen Welt in der Datenbank ermöglichen (s. [Ora98a]). Ein solcher Objekttyp besteht dabei aus drei Komponenten, nämlich dem Namen, um eine eindeutige Identifikation zu gewährleisten, den Attributen in Form von systemeigenen Datentypen oder weiteren benutzerdefinierten Objekttypen, welche die Struktur des neuen Typs festlegen, sowie den Methoden, die als spezielle Funktionen oder Prozeduren spezifische Operationen auf den Daten definieren (s. [Ora98a]). In diesem Abschnitt sollen die Möglichkeiten und Grenzen der neuen objekt-relationalen Features dargelegt werden. Dieses geschieht zwar noch unabhängig von der späteren Anwendung, jedoch beziehen sich die folgenden Erläuterungen im wesentlichen auf Prinzipien, die im Rahmen der Implementation anwendbar erscheinen und enthalten daher keine vollständige Beschreibung 40 4.1. OBJEKT-RELATIONALE MÖGLICHKEITEN 41 aller objekt-relationalen Neuerungen. Die Informationen wurden hauptsächlich der offiziellen Oracle-Dokumentation [Ora98a] bis [Ora99g] entnommen, wenngleich auch die unabhängigen Werke [HP98], [KL97] und [Urm97] vielfach hilfreich waren. 4.1.1 Kollektionen Kollektionen stellen eine Sammlung von Objekten eines einheitlichen Typs dar, wobei sowohl systemeigene Datentypen wie auch benutzerdefinierte Objekttypen verwendet werden können. Die wesentliche Einschränkung bei der Verwendung von Kollektionen ist, daß diese nicht geschachtelt werden können, d. h. Kollektionen von Objekten, die ihrerseits Kollektionen enthalten, sind nicht erlaubt. Kollektionen bestehen entweder aus varrays oder aus nested tables. Varrays entsprechen den in Hochsprachen üblichen Vektoren. Sie haben eine bei der Definition zu bestimmende Höchstzahl von Elementen, und die enthaltenen Objekte weisen eine feste Reihenfolge auf. Varrays werden physikalisch wie skalare Datentypen direkt je Tupel abgelegt, sofern sie eine gewisse Größe nicht überschreiten, ab der sie wie ein large object (lob) behandelt werden. Nested tables haben ähnliche Eigenschaften wie die gewöhnlichen Tabellen in Oracle. Sie haben keine voreingestellte Obergrenze bezüglich ihrer Elementanzahl, und die enthaltenen Objekte haben keine festgelegte Reihenfolge. Nested tables werden bei der Verwendung in Tabellen nicht wie skalare Datentypen je Tupel abgelegt. Stattdessen muß für jeden nested table-Typ die Definition einer stored table erfolgen. Die stored table (auch: innere Tabelle) nimmt dann alle Kollektionen eines nested table-Typs der äußeren Tabelle auf. Die innere Tabelle enthält eine implizite Spalte nested table id, die jeweils auf dasjenige Tupel der äußeren Tabelle verweist, zu der die Kollektion gehört. Die nachfolgenden Anweisungen erzeugen einen kollektionswertigen Typ telefon typ, der mehrere Telefonnummern aufnimmt und eine Beispieltabelle ang, die diesen Typ verwendet. In Abbildung 4.1 ist diese Konstruktion schematisch dargestellt1 . create type telefon_typ as table of varchar(20); create table ang ( nr number, name varchar(50), tel telefon_typ ) nested table tel store as ang_tel_st; Auf die inneren Tabellen kann unter SQL nicht direkt in der gewohnten Form mittels select zugegriffen werden. Stattdessen erfolgt der Zugriff stets über die äußere Tabelle und zwar mit Hilfe der Operatoren the und table. Dabei können diese Operatoren anstatt einer Tabelle in SQL-Anweisungen verwendet werden. Der Operator the liefert zu einer select-Anweisung die Tabelle eines kollektionswertigen Objektes. Die verwendete select-Anweisung muß dabei genau ein Tupel der äußeren Tabelle ergeben. Der Operator table wandelt ein kollektionswertiges 1 Das Kommando desc ang tel st liefert unter SQL*Plus den nicht dokumentierten Fehler SP2-0642: SQL*Plus internal error state 2131, context 0:0:0, unsafe to proceed. Die systemseitig generierte Spaltenbezeichnung column value kann man jedoch der Dokumentation [Ora99a, ch. 4] sowie den folgenden Beispielen entnehmen. 42 KAPITEL 4. PLANUNG UND ENTWURF DER IMPLEMENTATION Ang Nr Name 13 Meier Tel Verweis auf Spalte(n) in innerer Tabelle ang_tel_st nested_table_id ... 12 Müller 0511 1738 040 1717 020 1212 ... ... 27 column_value 0511 7384 0511 1244 ... Boss Verweise auf Spalte(n) in äußerer Tabelle 089 3014 Abbildung 4.1: Kollektionswertiges Attribut als nested table-Typ Attribut direkt in eine Tabelle um. Die folgenden Beispiele demonstrieren diese beiden indirekten Zugriffsmöglichkeiten mit der oben definierten Tabelle. select * from the(select tel from ang where nr = 12); select value(nt) from ang a, table(a.tel) nt where a.nr = 12; insert into the(select tel from ang where nr = 12) values (’089 42168’); select * from ang a where exists (select * from table(a.tel) nt where value(nt) like ’0511%’); Die ersten beiden Anfragen liefern beide dasselbe Ergebnis, nämlich alle Telefonnummern des Angestellen mit der Nummer 12. In der dritten Zeile wird der the-Operator verwendet, um für den Angestellten 12 eine weitere Telefonnummer hinzuzufügen. Im letzten Beispiel werden alle Angestellen gesucht, die unter einer hannoverschen Telefonnummer zu erreichen sind. Im Falle der obigen Beispiele liefert die Verwendung von value(nt) dasselbe Ergebnis wie nt.column value, da der Typ telefon typ nur aus einem einzigen skalarem Typ besteht. Weitere Zugriffsmöglichkeiten ergeben sich durch die Operatoren cast und multiset, die häufig kombiniert werden. Im nachfolgenden Beispiel liefern beide Anfragen dasselbe Ergebnis; multiset wandelt ein Anfrageergebnis in eine Kollektion, und cast nimmt eine explizite Typumformung vor. select tel from ang where nr = 12; select cast(multiset(select * from table(a.tel)) as telefon_typ) from ang a where a.nr = 12; Nach [HP98] wird bei stored tables für die Spalte nested table id vermutlich intern ein Index angelegt, um die Werte einzelner Kollektionen der äußeren Tabelle effizient aufzufinden — dieses läßt sich jedoch anhand der Oracle-Dokumentation Version 8.0 ([Ora98a] bis [Ora98e]) 4.1. OBJEKT-RELATIONALE MÖGLICHKEITEN Ang Nr Name 13 Meier Tel ang_tel_st ... 12 Müller ... 27 43 Boss nested_table_id column_value 0511 1244 0511 7384 ... 020 1212 040 1717 0511 1738 ... 089 3014 Abbildung 4.2: Nested table-Typ als index organized table (IOT) nicht belegen. Für die Version 8.1 wurden jedoch einige Änderungen bezüglich der physikalischen Speicherung von benutzerdefinierten Typen vorgenommen (s. [Ora99f]). Wie schon unter Version 8.0 können explizite Indexe auf stored tables angelegt werden, wie im untenstehenden Beispiel zu sehen ist. Darüberhinaus können nun nested tables unter Beteiligung der Spalte nested table id am Primärschlüssel als index organized tables (IOT) angelegt werden. Die stored table liegt dann stets sortiert vor, und jede einzelne Kollektion (d. h. alle inneren Tupel mit identischem Eintrag für nested table id) wird physikalisch in einem Cluster abgelegt. Weiterhin kann bei der Tabellenanlage durch das Schlüsselwort compress erreicht werden, daß je Kollektion nested table id nur einmalig gespeichert wird. In Abbildung 4.2 sind diese Eigenschaften dargestellt: Die Einträge sind nach nested table id und column value aufsteigend sortiert, je Kollektion wird nur ein Verweis gespeichert, und jede Kollektion findet in einem grau dargestellten Cluster Platz. Diese IOT -Konstruktion wird jedoch nicht standardmäßig eingerichtet, obwohl deren Verwendung von Oracle empfohlen wird. Stattdessen erreicht man diese wie im folgenden Beispiel gezeigt. Ein Index für die äußere Tabelle auf der Spalte mit dem kollektionswertigen Typ (in Beispiel und Abbildung ist dieses tel) wird systemseitig angelegt, die Verwendung eines Index auf nested table id in der inneren Tabelle muß explizit vereinbart werden und ist besonders im Zusammenhang mit der o. g. IOT -Konstruktion zu empfehlen (s. [Ora99c, ch. 18]). create unique index ang_tel_idx on ang_tel_st (nested_table_id, column_value); create table ang ( nr number, name varchar(50), tel telefon_typ ) nested table tel store as ang_tel_st ( (primary key (nested_table_id, column_value)) organization index compress); 44 4.1.2 KAPITEL 4. PLANUNG UND ENTWURF DER IMPLEMENTATION Referenzen Insbesondere wenn die Objektstrukturen stärker verschachtelt sind, z. B. wenn in einer Kollektion Adressen abgelegt werden sollen und jede Adresse aus mehreren Attributen wie Straße, Stadt und PLZ besteht, die ihrerseits wieder benutzerdefinierten Typs sind (etwa Typ Straße aus Straßenname und Hausnummer), lassen sich die Zusammenhänge mit Hilfe von Objekten einfacher modellieren als traditionell im relationalen Modell über Fremdschlüsselbedingungen. Informationen darüber, welche der beiden Methoden vom Datenbanksystem effizienter umgesetzt wird, waren leider Dokumentation wie Literatur nicht zu entnehmen. Diesbezüglich wären spezielle Performance-Tests vonnöten. Wie bereits im vorangehenden Abschnitt erwähnt, ist es nicht möglich, kollektionswertige Attribute zu schachteln. Diese Einschränkung ist recht hinderlich, zumal — wie gerade beschrieben — ein großer Vorteil der objekt-relationalen Technik genau darin besteht, auch bei komplexen und verschachtelten Modellierungen einen guten Überblick zu bieten. Es gibt (u. a.) daher die Möglichkeit, mit Referenzen von Objekten zu arbeiten. Jedes Objekt, das in der Datenbank abgelegt wird, erhält einen sogenannten Objektidentifikator (OID), eine eindeutige interne Kennung. Mit Hilfe dieser OIDs kann auf Objekte verwiesen werden und somit die Einschränkung bei geschachtelten Kollektionen umgangen werden. Referenzen existieren physikalisch in verschiedenen Ausführungen: Durch Einbeziehen der ROWID ist ein schnellerer Zugriff auf das referenzierte Objekte möglich, durch Einschränken der Referenz auf die Objekte einer bestimmten Tabelle benötigen die Verweise weniger Speicherplatz. Erweitert man das Beispiel des vorangehenden Abschnitts, kann man wie folgt Referenzen einsetzen, um eine mehrfache Schachtelung von kollektionswertigen Typen zu erreichen. create type telefon_objtyp as object (tel_liste telefon_typ); create type ang_objtyp as object ( nr number, name varchar(50), tel ref telefon_objtyp); create type ang_type as table of ang_objtyp; create table firma ( name varchar(50), ang ang_typ ) nested table ang store as firma_ang_st; Leider ist die Handhabung der Referenzen von Kollektionen im Vergleich zu einfachen Kollektionen relativ aufwendig, da stets mittels ref und deref zwischen Objekt und Objektreferenz gewechselt werden muß. Weiterhin ist es bei der Entwicklung von Methoden auf den verwendeten Objekten z. B. unter PL/SQL hinderlich, daß nur Objekte referenziert werden können, die bereits in einer Tabelle der Datenbank abgelegt sind, denn dynamisch mittels Standardkonstruktor generierte Objekte haben keinen Objektidentifikator. 4.1. OBJEKT-RELATIONALE MÖGLICHKEITEN 45 varray 321 17 99 407 83 622 105 19 67 278 x(1) x(2) x(3) x(4) x(5) x(6) x(7) x(8) x(9) x(10) 622 105 19 x(6) x(7) x(8) feste Obergrenze nested table nach Löschoperationen 321 x(1) x(2) 99 407 x(3) x(4) x(5) 278 x(9) unbegrenzt x(10) Abbildung 4.3: Zugriff auf Kollektionen unter PL/SQL (nach [Ora98d]) 4.1.3 Methoden Neben der Möglichkeit, komplexe Strukturen in Form von Objekten abzubilden, liegt in der Formulierung von Methoden auf diesen Objekten eine weitere wesentliche Stärke des objektrelationalen Modells. Durch Methoden können sehr spezifische Operationen für einzelne Objekttypen definiert werden. Tritt man noch einmal den Vergleich zwischen Kollektionen und Fremschlüsselkonstruktionen an, erfolgt mit Hilfe von Methoden eine automatische Kapselung der Algorithmen, so daß eine bessere Übersichtlichkeit und Wartbarkeit der Modellation gewährleistet wird. Die Formulierung von Methoden erfolgt z. B. mit der systemeigenen Programmiersprache PL/SQL. Zusammen mit den dort vorhandenen Kontrollstrukturen existieren erweiterte Zugriffsmöglichkeiten auf Objekte und insbesondere auf Kollektionen. Die zwei möglichen Arten der Kollektionen — varrays und nested tables — werden dabei prinzipiell gleichbehandelt, und deren Elemente können wie Vektoren in üblichen Hochsprachen adressiert werden. Die Reihenfolge der Elemente bei nested tables ist zwar zufällig, aber dennoch fest. Bleibender Unterschied ist, daß varrays lückenlos und begrenzt sind, die einzelnen Elemente also durch 1, . . . , n adressiert werden können, wenn n die festgelegte Höchstzahl ist. Dagegen können nested tables beliebig erweitert werden und Lücken aufweisen, wenn einzelne Elemente entfernt wurden, d. h. existieren n Elemente in einer solchen Kollektion, können die einzelnen Elemente mit 1, . . . , m adressiert werden, wobei u. U. m > n (s. a. Abbildung 4.3). Weiterhin stehen systemeigene Methoden zur Manipulation von Kollektionen unter PL/SQL zur Verfügung (collection methods). Dieses sind namentlich exists, count, limit, first, last, prior, next, extend, trim und delete. Mit deren Hilfe lassen sich z. B. einzelne Elemente auf Existenz überprüfen oder löschen, ebenso können in einer (nested table-) Kollektion zusätzliche freie Plätze angelegt werden. Eine wichtige Rolle spielen Methoden bei dem Vergleich von Objekten. Grundsätzlich lassen sich Objekte nur vergleichen, wenn sie gleichen Typs sind. Weiterhin ist standardmäßig nur der Vergleich auf Gleichheit und Ungleichheit möglich und dieser auch nur unter SQL, nicht jedoch unter PL/SQL. Dieser Vergleich funktioniert jedoch nicht immer wunschgemäß, wenn die Objekte Referenzen oder Kollektionen beinhalten, d. h. nur skalare Typen werden bezüglich identischer Inhalte verglichen. 46 KAPITEL 4. PLANUNG UND ENTWURF DER IMPLEMENTATION Da ein Vergleich bei vielen Operationen implizit enthalten ist (z. B. bei order by, distinct und Mengenoperationen) und diese ohne korrekte Vergleichsoperation somit ebenfalls nicht zur Verfügung stehen, hat man die Möglichkeit, zusätzliche objektspezifische Methoden zu formulieren, die den Vergleich typgleicher Objekte erlauben. Dafür sind zwei Varianten vorgesehen, nämlich sogenannte map- und order-Funktionen. Bei einer Objektdeklaration ist höchstens einer der Methodentypen erlaubt, und die zugehörige Funktion wird durch das Schlüsselwort map bzw. order gekennzeichnet. Eine map-Funktion bildet einen Objekttypen auf einen skalaren Typen (number, date oder varchar) ab. Beim konkreten Vergleich werden dann nach der Abbildung der beteiligten Objekttypen nur die zugehörigen skalaren Werte verglichen. Auf diese Weise können Vergleiche, bei denen einzelne Objekte mehrfach beteiligt sind (z. B. bei Sortierungen), relativ effizient ausgeführt werden, da die Abbildung auf ein Skalar nur einmal je Objekt erfolgt. In [Ora98d] findet man als einfaches Beispiel einen Objekttyp, der die rationalen Zahlen mit Hilfe der beiden numerischen Variablen Zähler und Nenner abbildet. Die zugehörige map-Funktion liefert den Quotienten Zähler/Nenner, so daß ein korrekter Vergleich rationaler Zahlen erfolgt. Eine order-Funktion vergleicht zwei typgleiche Objekte und liefert als Ergebnis kleiner, größer oder gleich. Es können so beliebige Vergleichsoperationen geschaffen werden. Deren Anwendung ist jedoch bei Mehrfachvergleichen erheblich aufwendiger als bei map-Funktionen, da für jeden Vergleich die order-Funktion explizit aufgerufen wird. Bei nested table-Typen fällt wegen der unbegrenzten Elementanzahl eine Abbildung auf skalare Werte häufig schwer, so daß nur der zeitintensivere Vergleich mittels order-Funktion bleibt. Map- und order-Funktionen müssen mit restrict references bezüglich ihrer Zugriffe eingeschränkt werden, um Seiteneffekte zu vermeiden. Es sind somit keine lesenden und schreibenden Zugriffe auf die Datenbank oder auf globale PL/SQL-Variablen erlaubt. 4.1.4 Oracle 8.1 vs. Oracle 8.0 In der Version 8.0 wurden in Oracle erstmalig objekt-relationale Features zur Verfügung gestellt. Weiterhin gehören diese Features nicht standardmäßig zum Datenbanksystem, sondern werden im Rahmen eines Zusatzpakets (object option) angeboten. Vermutlich sind diese beiden Punkte dafür verantwortlich, daß erstens der Umfang der umgesetzten objekt-orientierten Konzepte zunächst recht gering ausgefallen ist und zweitens die Zuverlässigkeit der neuen Features teilweise zu wünschen übrig läßt. Zur ersten Anmerkung sind vor allem fehlende Mechanismen zur Vererbung von Objekttypen, zur Objektidentifikation dynamisch konstruierter Objekte und zur Schachtelung von kollektionswertigen Objekten zu nennen. Sinnvoll wäre auch die Möglichkeit der wechselseitigen Verwendung von Objekttypen, d. h. Konstruktionen wie Typ A verwendet in seiner Methode AM den Typ B, und Typ B hat Typ A als Eingabeobjekt in der Methode BM. In [HP98] — ein recht frühes Werk zu Oracle 8.0 — wird näher auf wünschenswerte objekt-orientierte Funktionen eingegangen. Während der Tests zur Abbildung temporaler Strukturen wie Intervalle und temporale Elemente sind einige Fehler aufgetreten, die den Anlaß zu der zweiten obigen Anmerkung geben. Vor allem die Kombination von Sichten, mehrfach geschachtelten Objekten mit Referenzen und Methodenaufrufen erweisen sich als problematisch. So ist ein einfacher Zugriff auf eine solche Sicht möglich (select * from objview), während die Verwendung einer Auswahlbedingung zu einem Fehler führt (select * from objview where nr=1) (oracle bug-no. 988230). Weiterhin ist ein 4.2. VARIANTEN DES SPEICHERMODELLS 47 Zugriff über eine angelegte Sicht möglich (create view objview (...) und dann select * from objview), während der direkte Zugriff nicht funktioniert (select * from (...)) (Oracle bug-no. 988306). Als hinderlich haben sich auch die Probleme beim Sortieren von nested tableObjekten unter PL/SQL erwiesen (Oracle bug-no. 841108, 977211). Glücklicherweise bringt die neu verfügbare Version 8.1.5 gegenüber dem Vorläufer einige wesentliche Verbesserungen mit sich, so funktioniert hier die Sortierung von nested tableObjekten wenigstens eingeschränkt, d. h. eine aufsteigende Sortierung nach allen vorhandenen Attributen wird ausgeführt. Laut Oracle-Support ist das Problem der Sortierung ab der Version 8.1.6 behoben. Weiterhin ist unter SQL das sogenannte unnesting of collections möglich, d. h. für das Beispiel aus Abschnitt 4.1.1 ist nun eine Anfrage in der folgenden Art möglich, um alle Telefonnummern eines Angestellten aufzulisten: select a.nr, value(nt) from ang a, table(a.tel) nt;. Darüberhinaus existieren neue Möglichkeiten, die physikalische Speicherung von Objekten zu beeinflussen, siehe hierzu Abschnitt 4.1.1 sowie [Ora99c, ch. 18] und [Ora99f, ch. 2]. Eine weitere neue Funktion in der Version 8.1.5, die insbesondere für die Umsetzung der temporalen Erweiterung von SQL hilfreich sein könnte, sind temporäre Tabellen. Es können so Tabellen geschaffen werden, deren Inhalt nur für einen Benutzer sichtbar ist und — abhängig von der Definition der Tabelle — nur für die Dauer einer Sitzung oder einer Transaktion erhalten bleiben. Leider unterstützen temporäre Tabellen keine benutzerdefinierten Typen. Als sehr wichtige neue Funktion von Oracle 8.1.5 im Zusammenhang mit der Implementation des Übersetzungsprogramms erscheint die Verwendung dynamischer SQL-Kommandos unter Pro*C in Kombination mit benutzer-definierten Typen. Auf diese Technik wird in Kapitel 5 näher eingegangen; die offizielle Dokumentation findet man in [Ora99g]. Festzuhalten bleibt, daß ohne dieses Feature kaum temporale Daten außerhalb der Datenbank präsentiert werden können, da die übersetzten Anfragen notwendig dynamisch sind, d. h. erst zur Laufzeit des Programms feststehen. Außerdem sind an den auszugebenen Daten auf jeden Fall Objekttypen beteiligt, da deren Verwendung ein Ziel der vorliegenden Arbeit ist. In der Version 8.0.5 ist zwar die Nutzung dynamischer SQL-Kommandos unter Pro*C und auch die Übergabe von benutzer-definierten Typen vorgesehen, jedoch nicht die Kombination der beiden Techniken. Eine komplette Übersicht der Erweiterungen der Version 8.1 gegenüber 8.0 findet sich in [Ora99f]. 4.2 Varianten des Speichermodells In diesem Abschnitt wird die Art und Weise der Speicherung temporaler Tabellen für die Implementation in Kapitel 5 festgelegt. Dazu werden zunächst Anforderungen an das zukünftige Speichermodell formuliert, um dann zwei verschiedene Varianten vorzustellen. In beiden Varianten wird anhand von Beispielen zur Übersetzung temporaler Anfragen deren Funktionalität verdeutlicht. Abschließend werden die unterschiedlichen Ansätze bewertet und die Festlegung auf ein Speichermodell vorgenommen. Eine erste grundsätzliche Anforderung, die sich zwar nicht speziell aus der Festlegung des Speichermodells ergibt, diese jedoch maßgeblich beeinflußt, ist der Anspruch der gesamten Arbeit, die neuen objekt-relationalen Möglichkeiten von Oracle 8 zum Einsatz zu bringen. Der Zweck dieser Arbeit wäre eindeutig verfehlt, wenn ein rein relationales Speichermodell zum 48 KAPITEL 4. PLANUNG UND ENTWURF DER IMPLEMENTATION Einsatz käme, selbst wenn sich dieses für einzelne Anforderungen als einer möglichen objektrelationalen Variante überlegen herausstellen sollte. Als Anforderungen, die sich unmittelbar an das Speichermodell ergeben, sind vor allem eine einfache Übersetzbarkeit der temporalen Anweisungen und deren effiziente Ausführbarkeit zu nennen. Beide Punkte beschränken sich im wesentlichen auf die Anfrageausführung, da Datenmanipulationsanweisungen schon bei der Formulierung der Sprache (s. a. Abschnitt 3.2.3) keinen Schwerpunkt bilden und ihre Ausführung i. A. als weniger zeitkritisch erachtet wird. Die einfache Übersetzbarkeit bezieht sich sowohl auf die Übersetzung der temporalen Anfragen in ausführbare SQL-Kommandos, wie auch die Darstellung der Anfrageergebnisse, was man gewissermaßen als Rückübersetzung bezeichnen könnte. Für die Einfachheit beider Übersetzungsrichtungen erscheint offensichtlich ein Speichermodell angebracht, welches sich stark am semantischen Modell der Sprache orientiert. Für den Bereich der temporal aufwärtskompatiblen und sequentiellen Anweisungen kommt wegen der Ausführung bezüglich einzelner Zeitpunkte ein eher flaches Speichermodell mit mehr Redundanz bei den nicht-temporalen Informationen in Betracht, während für den Bereich der nicht-sequentiellen Anfragen durch die Konzeption der komplexen Tupel ein eher geschachteltes Modell sinnvoll erscheint. Eine effiziente Ausführbarkeit der übersetzten Anfragen wird vor allem durch eine effektive Vermeidung der sequentiellen Durchsicht (Relationenscan) aller vorhandenen Daten erreicht. Dieses begründet sich damit, daß im Vergleich zu nicht-temporalen Datenbanken in der Regel mehrere Versionen der Informationen mit denselbem zeitinvarianten Schlüssel existieren (s. a. [ZCF97, ch. 7]). Um die Menge der sequentiell durchzusehenden Daten wirksam einzuschränken, sollten also in einer Vorauswahl für die jeweilige Anfrage irrelevante Versionen aussortiert werden. Dieses könnte man etwa durch eine Indizierung der kleinsten und größten vorhandenen Zeitpunkte der Gültigkeitszeit je Informationseinheit (z. B. Tupel) erreichen. Eine solche Indizierung könnte entweder durch eine explizite Vorauswahl oder durch Hinweise an den Optimierer des DBMS bei der Übersetzung berücksichtigt werden. 4.2.1 Einfache Schachtelung In der Variante zum Speichermodell mit einfacher Schachtelung wird der Weg der flachen Speicherstruktur mit Redundanzen bei nicht-temporalen Informationen gewählt. Es werden dabei temporale Elemente als Zeitstempel auf Tupeln verwendet. Konkret wird eine Tabelle dabei um zwei Spalten ergänzt, damit diese temporale Informationen aufnehmen kann. Dieses sind namentlich rwo nr und ts , wobei die erste einen zusätzlichen internen Schlüssel darstellt, der die Zuordnung mehrerer physikalischer Tupel zu einem komplexen Tupel der Sprache ermöglicht (rwo = real world object). Die zweite zusätzliche Spalte ist der eigentliche Zeitstempel, der die Gültigkeitszeit des verwendeten Tupels beinhaltet und über einen nested table-Typen implementiert wird. In Abbildung 4.4 ist eine solche Konstruktion schematisch dargestellt (vgl. a. Abbildung 4.1). Die aus Kapitel 3 bekannte Tabelle Ang enthält dort eine kleine inhaltliche Erweiterung, um die Kollektionswertigkeit der Zeitstempel darstellen zu können: Der Angestellte Boss wird am 17.11.99 auf sein altes Gehalt zurückgesetzt. Der Inhalt von rwo nr ist rein zufällig und enthält nur die Information, daß die beiden Tupel zum Angestellten Boss einem einzigen komplexen Tupel und somit nur einem Objekt der realen Welt zuzuordnen sind. Weiterhin kann man der Abbildung entnehmen, daß eine bis auf weiteres gültige Information (forever oder ∞) durch 4.2. VARIANTEN DES SPEICHERMODELLS Ang Nr Name Gehalt rwo_nr_ 12 Müller 5000 379 13 Meier 6000 322 49 ts_ ang_ntabl_ nested_table_id vt_begin vt_end 05.10.1999 31.12.9999 05.10.1999 31.12.9999 27 Boss 5900 421 27 Boss 6400 421 05.10.1999 12.10.1999 17.11.1999 31.12.9999 12.10.1999 17.11.1999 Abbildung 4.4: Temporale Tabelle Ang im einfach geschachtelten Speichermodell das größte im System darstellbare Datum, nämlich den 31.12.99992 , repräsentiert wird. Für die Implementation der obigen Beispieltabelle sind die folgenden Definitionen notwendig. Man benötigt dabei das Objekt der Zeitstempel, das sich aus einer Menge von Intervallen zusammensetzt, sowie die eigentliche Tabellendefinition mit den genannten zusätzlichen Spalten und der nested table. Über diese Definitionen hinaus sind verschiedene Methoden auf den Objekten t interval type und t stamp type denkbar, wie z. B. temporale Schnitt- oder Differenzbildung und temporale Verschmelzung. create type t_interval_type as object ( vt_begin date, vt_end date); create type t_element_tabl as table of t_interval_type; create type t_stamp_type as object ( ts t_element_tabl); create table ang ( nr number, name varchar(20), gehalt number, rwo_nr_ number, ts_ t_stamp_type ) nested table ts_.ts store as ang_ntabl_; Nachdem das einfach geschachtelte Speichermodell am Beispiel vorgestellt wurde, sollen nachfolgend Möglichkeiten zur Indizierung der verwendeten Zeitstempel genannt werden. Abschließend wird das Modell durch exemplarische Übersetzungen für die einzelnen Erweiterungsebenen 2 Aus Platzgründen und im Sinne einer besseren Lesbarkeit wird auf die Angabe der Uhrzeit verzichtet. Präzise handelt es sich um den Zeitpunkt 31.12.9999 23:59:59, um einer sekundengenauen Granularität gerecht zu werden. 50 KAPITEL 4. PLANUNG UND ENTWURF DER IMPLEMENTATION der Sprache präzisiert. Die dabei eingesetzten Funktionen werden zunächst nicht explizit definiert, mit der jeweils vorhandenen kurzen Erläuterung sollte deren Anwendung jedoch verständlich sein. Indizierung Neben den in Abschnitt 4.1.1 erwähnten allgemeinen Möglichkeiten zur Indizierung und IndexOrganisierung von nested table-Typen ist vor allem durch die folgende Erweiterung eine effizientere Anfrageausführung zu erwarten. Der Typ t stamp type wird dabei um den kleinsten und den größten Zeitpunkt im Zeitstempel ergänzt, und es werden jeweils Indexe auf diesen neuen Spalten vereinbart. Nachfolgend finden sich die dazu benötigten erweiterten Definitionen für die Beispieltabelle Ang. Durch diese Konstruktion könnte bei der Anfrageausführung eine temporale Vorauswahl stattfinden, mit deren Hilfe die Menge der sequentiell zu lesenden Tupel deutlich reduziert werden kann. Die Einträge in tp begin und tp end könnten z. B. durch einen Trigger auf ang ntabl stets auf aktuellem Stand gehalten werden. create type ts tp_begin tp_end t_stamp_type as object ( t_element_tabl, date, date); create index t_begin_index on ang (ts_.tp_begin); create index t_end_index on ang (ts_.tp_end); Temporal aufwärtskompatible Anfragen Es soll hier exemplarisch die folgende, bereits aus Abschnitt 3.1.1 bekannte, Anfrage übersetzt werden3 . Der Ausführungszeitpunkt spielt für das Anfrageergebnis natürlich eine maßgebende Rolle, wird jedoch über die systemeigene Funktion sysdate automatisch generiert, so daß die Übersetzung davon unabhängig erfolgt. Die verwendete Methode intersection liefere den Schnitt zwischen Zeitstempel und Zeitpunkt oder NULL, falls dieser leer ist. select a1.Nr, a1.Name, a1.Gehalt, a2.Name from Ang a1, Ang a2 where a1.ChefNr = a2.Nr and a1.Gehalt > 5900; =⇒ select a1.Nr, a1.Name, a1.Gehalt, a2.Name from Ang a1, Ang a2 where a1.ChefNr = a2.Nr and a1.Gehalt > 5900 and a1.ts_.intersection(sysdate) is not null and a2.ts_.intersection(sysdate) is not null; Sieht man bei der Anlage jeder temporalen Tabelle bereits eine Sicht nach Art des folgenden Beispiels vor, so kann die Übersetzung weiter vereinfacht werden. Präzise beschränkt sich diese dann auf den Austausch der verwendeten temporalen Tabellen durch die zugehörigen Sichten, 3 Die Tabelle Ang benötigt dazu die ungekürzte Fassung mit der Spalte ChefNr wie in Abschnitt 3.1.1. 4.2. VARIANTEN DES SPEICHERMODELLS 51 so daß auf eine explizite Anpassung der where-Klausel verzichtet werden kann. Auch die Verwendung einer Vorauswahl mit Hilfe eines Indexes nach o. g. Muster könnte man auf diese Weise innerhalb der Sicht verbergen. create view ang_tuc_ ( select Nr, Name, Gehalt, ChefNr from Ang a where a.ts_.intersection(sysdate) is not null); Sequentielle Anfragen Auch hier wird auf die bekannte Anfrage als Beispiel zurückgegriffen. Die Übersetzung liefert mehrere Teilschritte, die unter Einsatz temporaler Tabellen nacheinander ausgeführt werden. Die zeitliche Verschmelzung, d. h. hier die Vereinigung aller wertgleichen Tupel mit identischer rwo nr , bleibt vorerst unberücksichtigt, sie wird stattdessen erst im Rahmen der Präsentation der Daten ausgeführt. Auf diese Weise werden zwar u. U. mehr Tupel belegt als nach zeitlicher Verschmelzung benötigt würden, dafür bedarf es je Anfrage aber nur eines einzigen Verschmelzungsvorgangs und nicht eines Verschmelzungsvorgangs je Subquery. Für die Anfrageausführung ist diese späte Verschmelzung unerheblich, da der Informationsgehalt der Tupel — verschmolzen oder nicht — derselbe ist. Werden an einer Anfrage mehrere Tabellen beteiligt, etwa wie im Beispiel durch einen Verbund, bedarf es bei der Zuordnung der physikalischen Tupel zu den komplexen Tupeln der Sprache besonderer Aufmerksamkeit. Durch den Verbund werden komplexe Tupel miteinander gepaart, und jedes Paar bildet so ein neues komplexes Tupel. Da jedes komplexe Tupel i. A. aus mehreren physikalischen Tupeln mit identischen Einträgen bei rwo nr besteht, enthält ein Verbund der physikalischen Tupel zunächst Paare von rwo nr . Um jedoch ein gültiges komplexes Tupel darzustellen, wird eine einzige rwo nr je physikalischem Tupel benötigt. Dieses erreicht man durch Schaffung einer neuen rwo nr für jedes Paar von alten rwo nr . Läßt man im Beispiel aus der Abbildung 4.4 die Zeitstempel unberücksichtigt und bildet den Eigenverbund der Tabelle Ang, so ergeben sich physikalisch 16 Paare, jedoch nur neun komplexe Paare, für die es neun neue rwo nr zu generieren gilt, damit diese eindeutig sind. In der folgenden Übersetzung der Beispielanfrage werden daher im ersten Schritt die verschiedenen möglichen Paarungen komplexer Tupel ermittelt und in der temporalen Tabelle rwotemp abgelegt. Der zweite Schritt stattet diese Paarungen mit neuen eindeutigen Einträgen für rwo nr aus. Diese neuen Einträge werden mit Hilfe der Sequenz rwo nr sqlte generiert. Im dritten Schritt wird schließlich der Verbund unter der Beteiligung der temporalen Tabelle ausgeführt. Die Methode intersection liefert in dieser Variante den Schnitt zweier temporaler Elemente oder NULL, falls dieser leer ist. validtime select a1.Nr, a1.Name, a1.Gehalt, a2.Name from Ang a1, Ang a2 where a1.ChefNr = a2.Nr and a1.Gehalt > 5900; =⇒ insert into rwotemp (select distinct a1.rwo_nr_, a2.rwo_nr_, NULL from Ang a1, Ang a2 where a1.ChefNr = a2.Nr and a1.Gehalt > 5900); 52 KAPITEL 4. PLANUNG UND ENTWURF DER IMPLEMENTATION update rwotemp set new_rwo_nr_ = rwo_nr_te_.nextval; select a1.Nr, a1.Name, a1.Gehalt, a2.Name as Chef, rt_.new_rwo_nr_ as rwo_nr_, a1.ts_.intersection(a2.ts_) as ts_ from Ang a1, Ang a2, rwotemp rt_ where a1.ChefNr = a2.Nr and a1.Gehalt > 5900 and a1.ts_.intersection(a2.ts_) is not null and a1.rwo_nr_ = rt_.rwo1 and a2.rwo_nr_ = rt_.rwo2; Nicht-Sequentielle Anfragen Bei nicht-sequentiellen Anfragen erscheint vor allem der explizite Umgang mit den Zeitstempeln als problematisch. Einfache Anfragen können wie im folgenden Beispiel übersetzt werden4 . Dort wird die Gültigkeitszeit validtime(a) für jedes komplexe Tupel dynamisch berechnet. Als sinnvoll könnte sich alternativ eine Abspeicherung aller Zeitstempel je Tabelle erweisen, so daß auf diese mittels rwo nr zugegriffen werden kann. Für die dynamische Berechnung wird die Funktion coal eingesetzt, die eine Menge von Zeigern auf Zeitstempel entgegennimmt (mehrere ref(ts ) im benutzerdefinierten Typ ts setref) und diese zu einem Zeitstempel zusammenfaßt. Dieser verschmolzene Zeitstempel mit dem Inhalt validtime(a) wird mit dem Intervall [88, 96) zum Schnitt gebracht. Genau wenn dieser Schnitt [88, 96) entspricht, ist die ursprünglich geforderte Bedingung validtime(a) contains period [88, 96) erfüllt. nonsequenced validtime select * from Ang a where validtime(a) contains period [88, 96); =⇒ select rwo_nr_, ts_, Nr, Name, Gehalt, ChefNr from Ang a where (intersection(coal( cast(multiset(select ref(ts_) from Ang a2 where a2.rwo_nr_ = a.rwo_nr_) as ts_setref)), [88, 96)) = [88, 96)); Als umfangreicheres Beispiel soll nachfolgend die abschließende Anfrage aus Abschnitt 3.2.2 übersetzt werden. Dort wurden zwei verschiedene Varianten vorgestellt, hier soll diejenige ohne Verbundbildung betrachtet werden, die den validtime-Opertor auf Unteranfragen anwendet. Die Gültigkeitszeiten werden also notwendiger Weise dynamisch berechnet. nonsequenced validtime select a1.Name from Ang a1 where validtime( validtime select * from Ang s1 where s1.Abt = Einkauf’ and s1.Nr = a1.Nr) contains period [1988, 1990) and validtime( validtime select * from Ang s2 where s2.Abt = Verkauf’ and s2.Nr = a1.Nr) contains period [1999, 2000); 4 Da die Übersetzungsbeispiele hier noch Entwurfscharakter haben, wird der besseren Lesbarkeit wegen auf eine korrekte Darstellung der Intervalle verzichtet. Tatsächlich wird das Intervall [88, 96) durch die Anwendung des Standardkonstruktors mittels t interval type(’01.01. 1988 00:00:00’, ’01.01. 1996 00:00:00’) erzeugt. 4.2. VARIANTEN DES SPEICHERMODELLS 53 =⇒ select rwo_nr_, ts_, a1.Name from Ang (intersection(coal(cast(multiset( select ref(ts_) from Ang a2 where as ts_setref)), [1988, 1990)) = (intersection(coal(cast(multiset( select ref(ts_) from Ang a3 where as ts_setref)), [1999, 2000)) = a1 where a2.Nr = a1.Nr and a2.Abt = ’Einkauf’) [1988, 1990)) and a3.Nr = a1.Nr and a3.Abt = ’Verkauf’) [1999, 2000)); Für die Verbundbildung ist es fraglich, wie eine Übersetzung im einfach geschachtelten Speichermodell vorzunehmen ist. Im Unterschied zum sequentiellen Fall findet hier keine zeitliche Schnittbildung der am Verbund beteiligten flachen Tupel statt, und es sollen auch solche Tupel miteinander kombiniert werden, die keinen zeitlichen Überlapp besitzen. Man benötigte gewissermaßen ein Äquivalent zum nicht-temporalen outer join, bei dem auch NULL-Werte bei der Verbundbildung beteiligt werden. Hier würden stattdessen bei der Verbundbildung flache Tupel kombiniert, die gar keine gemeinsame Gültigkeitszeit besitzen. 4.2.2 Doppelte Schachtelung In der Variante zum Speichermodell mit doppelter Schachtelung wird das Konzept der komplexen Tupel aus dem semantischen Modell der Sprache übernommen: Es werden dabei temporale Elemente als Zeitstempel auf Attributen verwendet. Wegen der zweifachen Schachtelung und der in Abschnitt 4.1.2 in diesem Zusammenhang geschilderten Notwendigkeit von Referenzen fallen die zugehörigen Definitionen deutlich komplizierter aus als im Speichermodell mit einfacher Schachtelung aus dem vorangehenden Abschnitt. Unverändert übernommen werden die Typen zur Modellierung der Zeitstempel, t interval type und t stamp type. Zusätzlich werden für die systemeigenen Typen date, number und varchar drei kollektionswertige Typen benötigt, die Paare von Werten und Zeitstempeln aufnehmen können. Diese werden nachfolgend als temporale Datentypen bezeichnet. Da diese Kollektionen keine weiteren Kollektionen enthalten dürfen, sind die Zeitstempel als Referenz auf Objekte vom Typ t stamp type realisiert. Die referenzierten Zeitstempel werden zentral in einer Tabelle stamps gesammelt. Insgesamt ergibt sich eine Konstruktion wie in Abbildung 4.5 am bekannten Beispiel dargestellt (auf die Nummer der Angestellten wurde nur einer besseren Übersicht wegen verzichtet). Die geraden Pfeile stellen dort, wie aus den vorangehenden Abbildungen bekannt, Bezüge zu inneren Tabellen dar. Zur Unterscheidung sind die Verknüpfungen, die über Referenzen realisiert werden, durch Kurven dargestellt. Die Tabelle stamps dient gewissermaßen als Wertevorrat für Zeitstempel und ist allen temporalen Tabellen der Datenbank zugänglich, so daß auch mehrfach verwendete Zeitstempel nur einfach abgelegt werden. Neben den Definitionen der Typen t interval type und t stamp type aus Abschnitt 4.2.1 benötigt man die folgenden Anweisungen, um die im Beispiel dargestellte temporale Tabelle anzulegen. create table stamps of t_stamp_type nested table ts store as stamps_ntabl_; create type t_tsnum_type as object ( val number, 54 Ang KAPITEL 4. PLANUNG UND ENTWURF DER IMPLEMENTATION Name Gehalt ang_gehalt_ntabl_ nested_table_id val 5000 ts 6000 5900 6400 ang_name_ntabl_ nested_table_id val Müller ts Meier Boss stamps_ntabl_ nested_table_id vt_begin vt_end 05.10.1999 31.12.9999 stamps ts 05.10.1999 12.10.1999 17.11.1999 31.12.9999 12.10.1999 17.11.1999 Abbildung 4.5: Temporale Tabelle Ang im doppelt geschachtelten Speichermodell ts ref t_stamp_type); create type t_num_tabl as table of t_tsnum_type; create type t_tschr_type as object ( val varchar2(4000), ts ref t_stamp_type); create type t_chr_tabl as table of t_tschr_type; create table ang ( name t_chr_tabl, gehalt t_num_tabl ) nested table name store as ang_name_ntabl_, nested table gehalt store as ang_gehalt_ntabl_; Das am Beispiel vorgestellte Speichermodell soll nun nachfolgend für die einzelnen temporalen Spracherweiterungsebenen durch entsprechende Funktionalität ergänzt werden. Alle dazu benötigten Funktionen werden zusammen in einem Paket (package) mit dem Namen vt formuliert. Auf diese Weise können sie gemeinsame paketinterne Variablen und Unterfunktionen benutzen, auf die von außerhalb des Pakets nicht zugegriffen werden kann. Das Aufrufen von Funktionen des Pakets erfolgt durch die Voranstellung von vt. vor die Funktionsnamen, so daß 4.2. VARIANTEN DES SPEICHERMODELLS 55 die Funktionen modulartig gekapselt sind. Im Paket vt sind die Funktionen snapshot, output, intersection und coalescence enthalten, deren Aufgaben nachfolgend beschrieben werden. Alle vier Funktionen machen von der Möglichkeit des overloading Gebrauch, d. h. sie existieren in verschiedenen Versionen mit unterschiedlichen Ein- und Ausgabeparametern und können dennoch jeweils über denselben Namen aufgerufen werden. Auf diese Weise können inhaltlich zusammengehörige Funktionen unter einem Namen zusammengefaßt werden, so daß sich die Anwendung des Pakets vereinfacht. Für die Beschreibung der Funktionen wird die oben definierte Beispieltabelle mit dem Inhalt wie in Abbildung 4.5 verwendet. In den ersten drei Beispielen werden die Ergebnistupel durch die etwas abstrakte Darstellung h . . . k . . . i angegeben, nur im letzten Beispiel entspricht das Ergebnis der tatsächlichen Bildschirmausgabe. snapshot liefert zu einem Objekt eines temporalen Datentyps und einem Zeitpunkt denjenigen skalaren Wert, der zum gegebenen Zeitpunkt gültig war, ist oder sein wird. Es wird nicht unterschieden, ob zu dem Zeitpunkt kein Wert vorhanden ist, oder ob der Wert zu dem Zeitpunkt NULL ist. In beiden Fällen wird NULL zurückgegeben. select vt.snapshot(Name, ’11.10.99’) vt.snapshot(Gehalt, ’11.10.99’) from Ang; =⇒ hMüller, 5000i, hMeier, 6000i, hBoss, 5900i intersection erlaubt die temporale Schnittbildung. Dabei ist es möglich, ein Objekt eines temporalen Datentyps mit einem Intervall oder einem temporalen Element zu schneiden oder zwei temporale Elemente zum Schnitt zu bringen. Ist ein Objekt mit temporalen Datentyp am Schnitt beteiligt, so hat der Rückgabewert denselben Typ. Das Ergebnis des Durchschnitts zweier temporaler Elemente ist ein temporales Element. Besteht zwischen den beiden Objekten keine zeitliche Überlappung, so wird NULL zurückgegeben. select vt.intersection(Gehalt, t interval type(’15.10.99’, ’15.12.99’)) from Ang; =⇒ h5000 k [15.10.99, 15.12.99)i, h6000 k [15.10.99, 15.12.99)i, h(6400 k [15.10.99, 17.11.99)), (5900 k [17.11.99, 15.12.99))i coalescence nimmt ein Objekt eines temporales Datentyps entgegen und vereinigt den Inhalt sowohl wertmäßig als auch temporal. Das heißt, es werden jeweils alle wertgleichen Einträge des Objektes zusammengefügt, indem die zugehörigen temporalen Elemente vereinigt werden. Diese temporale Vereinigung erfolgt unter Berücksichtigung der Minimalität der temporalen Elemente, so daß mögliche Überlappungen und Berührungen entfernt werden. Im folgenden Beispiel sei MyRound eine Hilfsfunktion, die alle skalaren numerischen Werte in einem Objekt vom Typ t num tabl auf volle Tausender rundet. select MyRound(Gehalt) from Ang; =⇒ h5 k [05.10.1999, 31.12.9999)i, h6 k [05.10.1999, 31.12.9999)i h(6 k [05.10.1999, 12.10.1999), [17.11.1999, 31.10.1999)), (6 k [05.10.1999, 31.12.9999))i select vt.coalescence(MyRound(Gehalt)) from Ang; =⇒ h5 k [05.10.1999, 31.12.9999)i, h6 k [05.10.1999, 31.12.9999)i h6 k [05.10.1999, 31.12.1999)i 56 KAPITEL 4. PLANUNG UND ENTWURF DER IMPLEMENTATION Das Beispiel zur Verschmelzungsoperation wirkt etwas konstruiert. Tatsächlich sind im komplexen Speichermodell die temporalen Datentypen per Definition stets verschmolzen. Die Verschmelzungsfunktion wird stattdessen auf Zwischenprodukte angewendet, um diese in reguläre Objekte zu transformieren und in der temporalen Datenbank ablegen zu können. output erzeugt zu einer temporalen Tabelle eine vorläufige Ausgabe, um die Daten direkt in SQL*Plus übersichtlich darzustellen. select vt.output(Name), vt.output(Gehalt) from Ang; =⇒ Name Gehalt ——————————————— ——————————————— Müller [05.10.1999, 31.12.9999) 5000 [05.10.1999, 31.12.9999) Meier [05.10.1999, 31.12.9999) 6000 [05.10.1999, 31.12.9999) Boss [05.10.1999, 31.12.9999) 5900 [05.10.1999, 12.10.1999) [17.11.1999, 31.12.9999) 6400 [12.10.1999 17.11.1999) Vorangehend wurden die datenbankseitig verwendeten Datentypen, Objekte und Funktionen vorgestellt. Es soll nun ein Überblick darüber gegeben werden, wie diese für die Umsetzung temporaler Funktionalität in den einzelnen temporalen Erweiterungsebenen verwendet werden. Temporal aufwärtskompatible Anfragen Die Anfrageverarbeitung in der Ebene der temporalen Aufwärtskompatibilität (TUC) erfolgt mit Hilfe der oben beschriebenen Funktion snapshot, der systemeigenen Funktion sysdate und einer Sicht je temporaler Tabelle. Für die bekannte Beispieltabelle lautet die Definition dieser Sicht wie folgt. create or replace view ang_tuc_ as select vt.snapshot(Nr, sysdate) as Nr, vt.snapshot(Name, sysdate) as Name, vt.snapshot(Gehalt, sysdate) as Gehalt from Ang where not (vt.snapshot(Nr, sysdate) is null and vt.snapshot(Name, sysdate) is null and vt.snapshot(Gehalt, sysdate) is null) Die Funktionsweise dieser Sicht dürfte unmittelbar verständlich sein: Für jedes temporale Attribut wird der gültige Wert zur aktuellen Systemzeit ermittelt und ausgegeben. Dem Anwender stellt sich ang tuc wie eine gewöhnliche nicht-temporale Tabelle dar. Durch die Verwendung der Funktion sysdate wird sichergestellt, daß je Anfrage durchgängig mit demselben Zeitpunkt gearbeitet wird und nicht durch die zeitliche Verzögerung der Anfrageausführung leicht unterschiedliche Zeitpunkte ausgewertet werden. Problematisch bleibt bei der obigen Definition die Verwendung des Wertes NULL. Wie deutlich zu sehen ist, wird zu einem Zeitpunkt, an dem snapshot für alle vorhandenen Attribute NULL liefert, die Ausgabe unterdrückt, d. h. der Anwender erhält als Ergebnis keine ausgewählten Zeilen. Es wird in diesem Fall davon ausgegangen, daß ein Zeitpunkt ausgewertet wurde, der in dem zugrundeliegenden temporalen Tupel nicht vorhanden ist. Es könnte aber auch möglich sein, 4.2. VARIANTEN DES SPEICHERMODELLS 57 daß zu dem vorhandenen Zeitpunkt tatsächlich alle gewünschten Attribute den Wert NULL besitzen. Um dieses Problem zu beheben, wäre ein komplexer Typ als Rückgabewert der Funktion snapshot denkbar, so daß unterschieden werden kann, ob es sich um einen leeren vorhandenen oder nicht vorhandenen Wert handelt. Am Prinzip der Funktion würde sich jedoch nichts ändern, nur die Definition der obigen Sicht würde etwas komplizierter ausfallen, so daß auf diese Maßnahme an dieser Stelle verzichtet werden soll. Sequentielle Anfragen Wie bereits erläutert, werden die Zeitstempel im komplexen Speichermodell über Referenzen realisiert. Da nur in der Datenbank abgelegte, nicht jedoch dynamisch produzierte, Objekte einen OID erhalten, müssen auch solche Zeitstempel in Tabellen abgelegt werden, die nur als Zwischenergebnis benötigt werden (s. a. Abschnitt 4.1.2). Innerhalb von select-Anweisungen ist jedoch die Verwendung von Funktionen und Prozeduren, die schreibend auf Tabellen der Datenbank zugreifen, nicht gestattet, um Seiteneffekte zu verhindern. Um diese Problematik zu umgehen, wird das folgende, bereits bekannte Beispiel übersetzt, indem die Ergebnisse zunächst in eine temporäre Tabellen eingefügt werden5 . Die temporäre Tabelle temp tab sei dazu bereits in geeigneter Weise angelegt. Weiterhin liege die Funktion intersection zur temporalen Schnittbildung in der Art vor, daß sechs Zeitstempel zugleich als Parameter entgegengenommen werden. Bei der Übersetzung werden je Paarung komplexer Tupel a und b alle sechs beteiligten Attribute (a.nr, a.name, a.gehalt, a.chefnr, b.nr, b.name) durch einen Verbund entschachtelt. Auf diesem Verbund wird der temporale Schnitt gebildet und die eigentliche Selektionsbedingung geprüft. Aus der gesamten Selektion werden schließlich ein Attribut und der gebildete Durchschnitt als Zeitstempel übernommen und nach temporaler Verschmelzung ausgegeben. Diese etwas unübersichtliche Entschachtelung inklusive Selektionsbedingung wird für jedes der vier Ausgabeattribute benötigt, da jeweils der Durchschnitt als neuer Zeitstempel benötigt wird. Für die letzten beiden Ausgabeattribute gehalt und chef ist daher auf die Wiederholung von Schnitt, Verbund und Auswahl zugunsten von ... verzichtet worden. validtime select a.Nr, a.Name, a.Gehalt, b.Name from Ang a, Ang b where a.ChefNr = b.Nr and a.Gehalt > 5900; =⇒ insert into temp_tab ( select vt.coalescence(cast(multiset( select nta1.val, vt.intersection(nta1.ts, nta2.ts, nta3.ts, nta4.ts, ntb1.ts, ntb2.ts) 5 Theoretisch macht dieser Umweg über eine Einfügeoperation keinen großen Sinn, denn warum sollte eine Selektionsanweisung mit schreibendem Datenbankzugriff in Kombination mit einer Einfügeanweisung zugelassen sein, wenn dieselbe Selektionsanweisung alleine abgelehnt wird? Tatsächlich funktioniert diese Konstruktion jedoch unter Oracle 8.1.5 und liefert in diesem Fall auch korrekte Ergebnisse. Eine wirklich gelungene Lösung wäre die der eigentlichen Abfrage vorausgehende Konstruktion und Abspeicherung aller benötigten Zeitstempel. Wegen des demonstrativen Charakters der hier dargestellten Übersetzungen soll darauf jedoch verzichtet werden. Fraglich bleibt in diesem Zusammenhang, ob es sich bei der Freizügigkeit der Version 8.1.5 um ein Feature oder einen Fehler handelt, denn Oracle 8.0.5 lehnt insert-Konstrukte der beschriebenen Art ab. 58 KAPITEL 4. PLANUNG UND ENTWURF DER IMPLEMENTATION from table(a.nr) nta1, table(a.name) nta2, table(a.gehalt) nta3, table(a.chefnr) nta4, table(b.nr) ntb1, table(b.name) ntb2 where vt.intersection(nta1.ts, nta2.ts, nta3.ts, nta4.ts, ntb1.ts, ntb2.ts) is not null and nta3.val > 5900 and nta1.val = ntb1.val) as t_num_tabl)) as nr, vt.coalescence(cast(multiset( select nta2.val, vt.intersection(nta1.ts, nta2.ts, nta3.ts, nta4.ts, ntb1.ts, ntb2.ts) from table(a.nr) nta1, table(a.name) nta2, table(a.gehalt) nta3, table(a.chefnr) nta4, table(b.nr) ntb1, table(b.name) ntb2 where vt.intersection(nta1.ts, nta2.ts, nta3.ts, nta4.ts, ntb1.ts, ntb2.ts) is not null and nta3.val > 5900 and nta1.val = ntb1.val) as t_chr_tabl)) as name, vt.coalescence(cast(multiset( select nta3.val, ... ) as t_num_tabl)) as gehalt, vt.coalescence(cast(multiset( select ntb1.val, ... ) as t_chr_tabl)) as chef from Ang a, Ang b); select vt.output(nr) as nr, vt.output(name) as name, vt.output(gehalt) as gehalt, vt.output(chef) as chef from temp_tab; Augenscheinlich liefern Übersetzungen dieser Art schnell sehr umfangreiche und unübersichtliche Ergebnisse. Weiterhin ist der Aufwand für die Entschachtelung je Paarung komplexer Objekte schlecht abzuschätzen. Um diesen zu verringern, kann man in dem obigen Beispiel für die äußere Selektionsanweisung die folgende where-Klausel hinzufügen, um eine Vorauswahl unter den komplexen Tupeln zu schaffen. where exists (select * from table(a.gehalt) nta3 where nta3.val > 5900) and exists (select * from table(a.chefnr) nta4 where nta4.val in (select ntb1.val from table(b.nr) ntb1)) Nicht-Sequentielle Anfragen Wie auch im Falle der nicht-sequentiellen Anfragen im einfach geschachtelten Speichermodell soll zunächst die folgende Anfrage übersetzt werden. Auch hier ergibt sich als einzige Herausforderung die Berechnung der Operators valitime(a). Zu diesem Zweck wird der Funktion coalescence im folgenden Beispiel als Wert stets NULL übergeben, so daß aufgrund der Wertgleichheit alle Zeitstempel verschmolzen werden. Die Berechnung wird wie in Abschnitt 4.2.1 dynamisch vorgenommen, effizienter wäre für physikalische komplexe Tupel die Ablage eines zusätzlichen Zeitstempels, der stets die verschmolzene Vereinigung aller Attributzeitstempel enthält. nonsequenced validtime select * from Ang a where validtime(a) contains period [88, 96); =⇒ 4.2. VARIANTEN DES SPEICHERMODELLS 59 select * from Ang a where vt.intersection(vt.coalescence(cast(multiset( select NULL, nt1.ts from table(a.nr) nt1 union select NULL, nt2.ts from table(a.name) nt2 union select NULL, nt3.ts from table(a.gehalt) nt3 union select NULL, nt4.ts from table(a.chefnr) nt4) as t_num_tabl)), [88, 96)) = [88, 96); Abschließend soll ein Ansatz für die Übersetzng der folgenden, bereits bekannten Anfrage gegeben werden. Die sequentiellen Teilanfragen wurden dabei geringfügig von * auf s.Abt bzw. t.Abt verändert, um die Übersetzung kürzer zu halten — der Sinn der Abfrage ändert sich dadurch nicht. Die Funktion intersection wird in der eingangs beschriebenen Variante benötigt, die ein Objekt temporalen Datentyps mit einem Intervall zum Schnitt bringt. Abgesehen von der aufwendigen Entschachtelung in den sequentiellen Teilanfragen läßt sich der nicht-sequentielle Verbund einfach übersetzen. Es ergeben sich hier keine Probleme wie im Falle des einfachen Speichermodells, da die komplexen Tupel ohne Rücksicht auf zeitlichen Überlapp kombiniert werden können. nonsequenced validtime select a.Name from Ang a, (validtime select s.Abt from Ang s where s.Abt = ’Einkauf’ and s.Nr = a.Nr) b, (validtime select t.Abt from Ang t where t.Abt = ’Verkauf’ and t.Nr = a.Nr) c where validtime(b) contains period [1988, 1990) and validtime(c) contains period [1999, 2000); =⇒ select a.Name from Ang a, (select vt.coalescence(cast(multiset( select nts1.val, vt.intersection(nts1.ts, nts2.ts, nta1.ts) from table(s.Abt) nts1, table(s.Nr) nts2, table(a.Nr) nta1 where vt.intersection(nts1.ts, nts2.ts, nta1.ts) is not null and nts1.val = ’Einkauf’ and nts2.val = nta1.val) as t_chr_tabl)) as abt from Ang s1) b, (select vt.coalescence(cast(multiset( select ntt1.val, vt.intersection(ntt1.ts, ntt2.ts, nta1.ts) from table(t.Abt) ntt1, table(t.Nr) ntt2, table(a.Nr) nta1 where vt.intersection(ntt1.ts, ntt2.ts, nta1.ts) is not null and ntt1.val = ’Verkauf’ and ntt2.val = nta1.val) as t_chr_tabl)) as abt from Ang s1) c where vt.intersection(b.Abt, [1988, 1990)) = [1988, 1990) and vt.intersection(c.Abt, [1999, 2000)) = [1999, 2000); 4.2.3 Festlegung Nach der prinzipiellen Vorstellung der beiden Speichermodelle, deren Präzisierung durch Übersetzungsbeispiele und Erläuterung von zusätzlichen Optionen wie Indizierung oder redundantem Vorhalten von Zeitstempeln in den beiden vorangehenden Abschnitten, sollen nun deren Vorund Nachteile abgewogen und ein Modell für die Implementierung ausgewählt werden. 60 KAPITEL 4. PLANUNG UND ENTWURF DER IMPLEMENTATION Zum Speichermodell mit einfacher Schachtelung lassen sich die Erkenntnisse auf die folgenden wesentliche Punkte reduzieren: • Darstellung durch einfache Datentypen, flache Darstellung; daher vermutlich geringere Probleme mit Fehlerhaftigkeit des DBMS • leicht temporal indizierbar • übersetzte Anfragen haben übersichtlichen Umfang • aufwendige Zuordung zu komplexen Tupeln mittels RWO-Nr. • Redundanz der Daten • zu erwartende Probleme bei nicht-sequentiellen Anfragen, z. B. Verbundbildung Das Speichermodell mit zweifacher Schachtelung kann durch die nachfolgend aufgezählten Erfahrungen charakterisiert werden: • keine Redundanz der Daten • einfache zeitliche Verschmelzung, da diese für einzelne Attribute vorgenommen wird • modellinhärente Zuordnung zu komplexen Tupeln • explizite Speicherung aller verwendeten Zeitstempel notwendig, wegen REFs • Übersetzung liefert sehr umfangreiche Anfragen mit aufwendiger Entschachtelung • komplizierte temporale Datentypen, daher verstärkt Probleme durch Fehlerhaftigkeit des DBMS möglich Insgesamt kann man sagen, daß keiner der beiden Kandidaten durch deutliche Vorteile hervorsticht. Jedoch erscheint das geschachtelte Speichermodell wegen der schnell sehr aufwendigen Übersetzungen im sequentiellen Bereich und der Unwägbarkeiten der Leistungsfähigkeit der hier in höherem Maße eingesetzten neuen objekt-relationalen Funktionen als die weniger erfolgversprechende Variante. Auch das Problem der stets explizit abzuspeichernden Zeitstempel, um diese mit Referenzen belegen zu können, läßt sich kaum effizient vermeiden. Es soll daher für die im Kapitel 5 beschriebene Implementation das Speichermodell mit einfacher Schachtelung zum Einsatz kommen. 4.3 Schichtenarchitektur In [TJB97] finden sich einige Konzepte zur Implementation einer temporalen Datenbanksprache auf einem vorhandenen Datenbankmanagementsystem. Die dort als Schichtenarchitektur (layered architecture) bezeichnete Technik ist in Abbildung 4.6 dargestellt. Die Funktionalität dieser Architektur ist in ihren Grundzügen schnell erläutert: Die Schicht oberhalb des DBMS nimmt eine temporale Anfrage entgegen, analysiert und übersetzt diese mit Hilfe entsprechender Informationen aus der Datenbank in eine einzige SQL-92 Transaktion, um diese ohne weiteren 4.3. SCHICHTENARCHITEKTUR 61 temporale Anfrage Q Fehlermeldung Ergebnis Schicht Scanner MetadatenManagement Parser AusgabeVerarbeitung Code Generator SQL-92 Anfrage, Q’ DBMS Abbildung 4.6: Schichtenarchitektur temporaler Datenbanken (nach [TJB97]) Einfluß des Übersetzters auf dem DBMS auszuführen. Schließlich wird das Ergebnis zurückgeliefert, nachbearbeitet und ausgegeben. Neben den hier sprachinhärenten Forderungen von Aufwärtskompabilität und temporaler Aufwärtkompabilität und damit der stufenweisen Verfügbarkeit von temporaler Funktionalität werden in [TJB97] vier weitere Ziele der dort verwendeten Schichtenarchitektur genannt. Dieses ist erstens die möglichst weitgehende Verwendung der Funktionalität des unterliegenden DBMS, so daß nur Features, die nicht im DBMS zu finden sind, neu implementiert werden müssen. Zweitens wird die Beibehaltung aller wünschenswerten Eigenschaften des relationalen DBMS genannt; es soll durch die temporale Erweiterung tatsächlich Funktionalität hinzugefügt werden. Drittens soll Plattformunabhängigkeit erreicht werden, um die Erweiterung auf allen SQL-92fähigen DBMS verfügbar zu machen. Schließlich wird eine adäquate Performance angestrebt, genauer sollen nicht-temporale Anfragen ebenso schnell ablaufen wie im zugrundeliegenden DBMS und temporale Anfragen in etwa der gleichen Zeit ausgeführt werden, wie entsprechende nichttemporale Anfragen auf einer nicht-temporalen Datenbank mit demselben Informationsgehalt. Das Ziel der Plattformunabhängigkeit kann in der vorliegenden Arbeit nur eingeschränkt übernommen werden, da die Implementation von vorherein auf die objekt-relationale Funktionalität von Oracle ausgerichtet ist. Insofern sollte eine Verwendung auf allen Plattformen mit Oracle und der object option möglich sein. Das Ziel der Portierbarkeit kann aber hier dennoch kaum als vordringlich bezeichnet werden, da es zunächst um die generelle Verfügbarkeit eines Prototypen geht. 62 KAPITEL 4. PLANUNG UND ENTWURF DER IMPLEMENTATION Die weitestgehende Verwendung der Funktionalität des unterliegenden DBMS ist sicherlich auch hier anzustreben, wenngleich das Nachkommen dieser Forderung — wie schon in [TJB97] festgestellt — vermutlich den Performancezielen entgegenwirken wird. Dennoch erscheint es gerade im Sinne einer Beschränkung des Entwicklungsumfangs angebracht, nach Möglichkeit der Verwendung von vorhandener Datenbankfunktionalität gegenüber Neuentwicklungen den Vorrang zu geben. Das zweite Ziel der Beibehaltung aller wünschenswerten Eigenschaften des eingesetzten relationalen DBMS, so daß durch die temporale Erweiterung tatsächlich Funktionalität hinzugefügt wird, ist prinzipiell sinnvoll. Für eine prototypische Implementation erscheint jedoch die Erhaltung vorhandener nicht-temporaler Features nicht vordringlich, da es um die Möglichkeit von Tests temporaler Funktionalität geht. Eher im Rahmen einer Kommerzialisierung wird man vom neuen System die Erhaltung aller nicht-temporalen Features erwarten. In [TJB97] findet man eine interessante Technik, um den Entwicklungsaufwand im Rahmen zu halten, gleichzeitig aber für aufwärtskompatible Sprachkonstrukte die komplette Funktionalität des unterliegenden DBMS zur Verfügung zu stellen. Mit Hilfe eines unvollständigen Parsers (partial parser) werden nur temporale Anweisungen erkannt. Sobald beim Parsen einer Anweisung ein Fehler auftritt, wird diese an das DBMS zur Ausführung weitergegeben in der Annahme, es handele sich um eine aufwärtskompatible Anweisung. Auf diese Weise bedarf es keiner eigenen Entwicklung eines SQL-92 Parsers, und gleichzeitig werden bei der Ausführung von aufwärtskompatiblen Kommandos keine zwei Parser-Durchläufe mehr benötigt. Leider gehen bei dieser Konstruktion fehlerhafte temporale und aufwärtskompatible Anweisungen denselben Weg, so daß man bei fehlerhaften temporalen Kommandos wenig aussagekräftige Fehlermeldungen erhalten wird. Um dieses Problem zu umgehen, werden in [TJB97] die Anweisungen um die Hinweise plain oder temporal ergänzt. So kann vorab entschieden werden, ob der eigene oder der Parser des DBMS die Anweisung erhält. Durch diese Hinweise geht jedoch die (temporale) Aufwärtskompatibilität verloren. Insgesamt erscheint es hier zur Reduzierung des Entwicklungsaufwandes sinnvoller, einen vollständigen Parser mit nur eingeschränkter SQL-92-Unterstützung zu entwickeln, als einen unvollständigen Parser mit den o. g. Nachteilen in Kauf zu nehmen. Insgesamt ist die in Abbildung 4.6 dargestellte Architektur für einen ersten Ansatz zur Modularisierung der Implementation dieser Arbeit nutzbar. Dem Code Generator sollten dabei auch die Informationen des Metadaten-Management zur Verfügung stehen, da z. B. zur Unterscheidung einer Anweisung zwischen aufwärtskompatibel und temporal aufwärtskompatibel die beteiligten Tabellen ausschlaggebend sind. Weiterhin sollten auch Scanner und Code Generator im Rahmen der lexikalen bzw. semantischen Analyse in der Lage sein, Fehlermeldungen zu generieren. Wie bereits eingangs argumentiert, ist die Plattformunabhängigkeit der Implementation kein vorrangiges Ziel der vorliegenden Arbeit. Es wird daher kein besonderer Wert darauf gelegt, daß der Code Generator reine SQL-92 Anweisungen liefert. Stattdessen ist die Verwendung Oraclespezifischer Features und insbesondere der Nutzung eigener Datenbankfunktionen vorgesehen. Auf diese Weise können Funktionen — etwa in PL/SQL formuliert — innerhalb des DBMS ausgeführt werden. Dadurch ist ein vereinfachter Umgang mit den beteiligten Objekten gewährleistet. Darüberhinaus erscheinen speicherplatzintensive Funktionen — z. B. Sortiervorgänge im Rahmen einer temporalen Verschmelzung — besonders für größere Tabellen innerhalb des DBMS besser aufgehoben, da außerhalb ein beträchtliches Maß an eigener Speicherverwaltung zu implementieren wäre. Es wird daher ein Teil des Codes innerhalb des DBMS in Form von Methoden auf den verwendeten Objekten und weitergehenden Funktionen formuliert werden. Kapitel 5 Implementation In diesem Kapitel wird die prototypische Implementation der in Kapitel 3 formulierten temporalen Erweiterung SQLTE auf der Grundlage des im vorangehenden Kapitel entwickelten Entwurfs beschrieben. Dabei wird zunächst ein Überblick des gesamten Programmablaufs gegeben. Anschließend erfolgt die Erläuterung der verwendeten Strukturen innerhalb der Datenbank und des Übersetzungsprogramms außerhalb der Datenbank. Zum Abschluß des Kapitels werden erste praktische Erfahrungen mit der neuen temporalen Datenbank anhand umfangreicherer Testdaten vorgestellt. 5.1 Programmübersicht In Abschnitt 4.3 wurde die in [TJB97] beschriebene Schichtenarchitektur für temporale Datenbanken vorgestellt, an der sich die vorliegende Implementierung orientiert. Es wurde dort bereits auf die Notwendigkeit einiger Veränderungen hingewiesen. Auf dieser Basis ergibt sich die Konstruktion wie in Abbildung 5.1 dargestellt: Zu sehen sind die sieben verwendeten Module, die Datenbank mit den dort definierten Strukturen sowie der Datenfluß zwischen den genannten Komponenten. Der Programmablauf läßt sich wie folgt zusammenfassen. Nach dem Programmstart erfolgt die Anmeldung an die Datenbank. Ist diese abgeschlossen, wird die Existenz der notwendigen SQLTE-Datenbankstrukturen im Schema des angemeldeten Benutzers überprüft. Nach Abschluß dieser einmaligen Initialisierung werden SQLTE-Kommandos entgegengenommen. Diese werden zeichenweise zerlegt an den Scanner übermittelt, der einzelne Zeichen zu gültigen Symbolen zusammenfaßt oder eine Fehlermeldung generiert, falls dieses nicht möglich ist. Die Symbole überprüft der Parser nach dem Prinzip des rekursiven Abstiegs auf Syntaxübereinstimmung zu SQLTE und erzeugt dabei eine Baumstruktur aus den gelesenen Symbolen. Mit Hilfe von Metadaten aus der Datenbank wird dieser Parserbaum im Codegenerator semantisch erweitert, um daraufhin rekursiv entlang der Knoten des Baumes die SQLTE-Befehle in SQL-Kommandos zu übersetzen. Die so erzeugten Kommandos werden schließlich durch das Modul Oracle auf der Datenbank ausgeführt. Bei Anfragen erfolgt die formatierte Ausgabe der Ergebnisse. Die Funktionalität der Implementierung ist damit zweigeteilt: Der oben beschriebene Programmablauf vollzieht sich bis zur vollendeten Übersetzung im wesentlichen außerhalb der Datenbank. Danach werden die übersetzten Anweisungen mit Hilfe der innerhalb der Datenbank 63 64 KAPITEL 5. IMPLEMENTATION Eingabe SQLTE-Kommandos als Eingabezeichen Anmeldung lexikalische Fehler Symbole SQLTE Initialisierungfehler Parser Parserbaum Anmeldung Oracle DB-Zugriffe Scanner Ergebnisse Metadaten SQL-Kommandos CodeGen Anfrageergebnisse SQLTE-Package semantische Fehler syntaktische Fehler SQLTE-Objekte Datenbank Ausgabe Abbildung 5.1: Datenflußdiagramm der SQLTE-Implementation mit PL/SQL definierten Methoden und Funktionen ausgeführt. An dieser Zweiteilung der Funktionalität orientieren sich die folgenden Erläuterungen, d. h. es folgt zunächst der Abschnitt über die Datenbankstrukturen, um anschließend die einzelnen Module zu erläutern. 5.2 Datenbankstrukturen In diesem Abschnitt werden die Strukturen vorgestellt, die auf der Seite der Datenbank benötigt werden, um die temporale Erweiterung SQLTE umzusetzen. Diese Strukturen bilden die Grundlage der temporalen Funktionalität und dienen dem in Abschnitt 5.3 beschriebenen Übersetzungsprogramm dazu, dem Anwender die komplette temporale Datenbanksprache zur Verfügung zu stellen. Die datenbankseitigen Erweiterungen werden sämtlich unter SQL und PL/SQL erstellt. Vor der ersten Verwendung des SQLTE-Übersetzers werden diese durch die Ausführung der Skriptdatei sqlte.sql unter SQL*Plus im zugrundeliegenden Datenbankschema erzeugt. Es bedarf dafür entsprechender Rechte des Datenbanknutzers im eigenen Schema, deren Existenz z. B. durch die Anwendung der vordefinierte Rolle connect sichergestellt werden. Nachfolgend werden zunächst grundsätzliche Festlegungen für die temporalen Strukturen 5.2. DATENBANKSTRUKTUREN 65 getroffen. Anschließend erfolgt eine Vorstellung der beteiligten Objekte und Funktionen. Die Unterscheidung zwischen Methoden in Abschnitt 5.2.2 und Funktionen in Abschnitt 5.2.3 erscheint vielleicht verwunderlich. Sie ist jedoch erforderlich, da der Ansatz, möglichst die gesamte Funktionalität objekt-orientiert, d. h. mit Hilfe von Objekten samt ihrer zugehörigen Methoden umzusetzen, leider keinen Erfolg gezeigt hat. Einige Programmteile lassen sich nicht durch Methoden auf den entsprechenden Objekten, wohl aber als eigenständige Funktionen definieren (s. a. Abschnitt 4.1). 5.2.1 Grundlegende Vereinbarungen Es werden nun grundsätzliche Festlegungen vorgestellt, die Voraussetzung für das Verständnis der Datentypen, Objekte und Funktionen sind. Sofern sich die Motivation der einzelnen Punkte nicht unmittelbar aus den Überlegungen der vorangehenden Kapitel ergibt, erfolgen entsprechende Erläuterungen in den späteren Abschnitten, wenn die einzelnen Vereinbarungen benötigt werden. • Es werden physikalisch temporale Elemente als Zeitstempel auf Tupeln verwendet (s. a. Speichermodell in Abschnitt 4.2.1). Temporale Elemente bestehen aus einer endlichen Menge disjunkter temporaler Intervalle, die einander nicht berühren, d. h. die temporalen Elemente sind stets verschmolzen (s. Definition 2 in Abschnitt 2.2.1). • Die Granularität wird durch den kürzesten darstellbaren Zeitpunkt von einer Sekunde festgelegt. • Der früheste darstellbare Zeitpunkt ’01.01.-4712 00:00:00’ wird als −∞ (beginning) definiert und bezeichnet eine Gültigkeitszeit mit unbekanntem Anfangspunkt in der Vergangenheit. Dieser Zeitpunkt ist nur als Anfangspunkt temporaler Intervalle zugelassen. • Der späteste darstellbare Zeitpunkt ’23.12.9999 23:59:59’ wird als ∞ (forever) definiert und bezeichnet eine Gültigkeitszeit mit unbekanntem Endpunkt in der Zukunft. Dieser Zeitpunkt ist nur als Endpunkt temporaler Intervalle zugelassen (s. a. Definition 1 in Abschnitt 2.2.1). • Ein komplexes Tupel der Sprache enthält stets die gesamte Historie eines Objektes der realen Welt. Daher müssen alle Attributausprägungen eines Tupels stets denselben Zeitraum abdecken. Somit ist der kleinste und der größte Zeitpunkt für alle Attributausprägungen eines Tupels identisch, und es sind zwischen diesen Zeitpunkten keine Lücken zugelassen. 5.2.2 Objekte Es werden die Typen t interval type, t element tabl und t stamp type verwendet, wie sie in Abschnitt 4.2.1 definiert und beschrieben sind. Zusätzlich vereinbart werden für Intervalle die Methode ti intersection sowie für temporale Elemente die Methoden tp intersection, ti intersection, te intersection und ti difference. Dabei sind die intersection-Funktionen für die temporale Schnittbildung bei den verschiedenen Typkombinationen verantwortlich, z. B. bringt die Methode ti intersection des Typs t stamp type ein Intervall mit einem temporalen Element zum Schnitt. Es steht somit ti für ein Intervall, tp bedeutet Zeitpunkt und te meint ein temporales Element. 66 KAPITEL 5. IMPLEMENTATION Die Differenzbildung zwischen zwei Intervallen ist nicht als Methode von t interval type zu finden (nach der obigen Namensgebung hätte diese dann ti difference geheißen), da das Ergebnis ein temporales Element also vom Typ t stamp type gewesen wäre. Die Methodendefinition benötigte daher eine wechselseitige Verwendung der Typen t interval type und t stamp type, was in Oracle leider nicht vorgesehen ist (s. a. Abschnitt 4.1). Statt der Methode wurde die Funktion ti subtraction formuliert (s. u.), deren Bezeichnung nicht ganz mit der Namensgebung der Methoden harmoniert. Eine Übersicht der Methoden mit detaillierter Beschreibung der vereinbarten Parameter findet sich in Anhang B. 5.2.3 Funktionen Alle benötigten Funktionen außerhalb der Methoden aus Abschnitt 5.2.2 werden in dem Package sqlte formuliert. Nachfolgend sollen diese kurz vorgestellt werden, präzise Definitionen finden sich wiederum in Anhang B. ti subtraction liefert die Differenz zweier temporaler Intervalle in dem Rückgabewert von Typ t stamp type. Diese Funktion kommt z. B. beim temporalen delete oder update zum Einsatz, wenn Teile von Zeitstempeln ausgeblendet werden sollen. ts2string wandelt ein temporales Element durch Aneinanderreihen der vorhandenen Intervalle in eine Zeichenkette um. Dabei werden entsprechende Trennzeichen eingefügt. Die Funktion dient als Provisorium zur objektfreien Übergabe von temporalen Elementen an das Übersetzungsprogramm in Pro*C (s. a. Abschnitt 5.3.7). te intersection macht von der Funktion des overloading Gebrauch (s. a. Abschnitt 4.2.2) und existiert in vier verschiedenen Versionen: Auf diese Weise können bis zu fuenf temporale Elemente zum Schnitt gebracht werden. Die Funktion basiert auf der Methode te intersection und stellt nicht wirklich neue Funktionalität zur Verfügung, sondern dient nur einer bequemeren Übersetzung. Neben der hier vorgesehenen variablen Anzahl der Eingabeparameter läßt sich eine Funktion im Gegensatz zu einer Methode ohne explizite Angabe eines Tabellenalias aufrufen, d.h. die Formulierung ts1.te intersection(ts2) ist nicht ausreichend, sondern es bedarf stattdessen alias.ts1.te intersection(ts2). Die temporale Schnittbildung kommt bei der Übersetzung von SQLTE vielfältig zum Einsatz, so z. B. beim sequentiellen Verbund oder bei sequentiellen Subqueries. 5.2.4 Temporäre Tabellen Die Übersetzung der SQLTE-Kommandos liefert in vielen Fällen Resultate, die sich über mehrere SQL-Befehle unter Verwendung entsprechender Zwischenergebnisse erstrecken. Für die Ablage dieser Zwischenergebnisse bietet sich die Verwendung von temporären Tabellen als neues Feature von Oracle 8.1.5 an (s. a. Abschnitt 4.1). Von vornherein fest vereinbart wird daher eine temporäre Tabelle mit Hilfe der folgenden Anweisung. Sie nimmt nur Werte vom Typ rowid auf und kann somit zur Auswahl bestimmter Tupel etwa im Rahmen von temporalen DML-Anweisungen dienen. Weiterhin werden während 5.3. PROGRAMMBESCHREIBUNG Oracle 67 CodeGen SQLTE Info Ausgabe Prompt Fehler Ergebnisse Eingabe Kommando Anmeldung Parser Scanner Abbildung 5.2: Abhängigkeitsgraph der Module der Übersetzung dynamisch temporäre Tabellen erzeugt, die sich mit Hilfe der Anweisung create table as bezüglich Attributname, -anzahl und -typ nach der jeweiligen Anfrage richten (s. a. Abschnitt 5.3.6). create global temporary table sqlte_rowidtemp_ (tupel rowid) on commit delete rows; 5.3 Programmbeschreibung In Abschnitt 5.1 wurde anhand der Abbildung 5.1 bereits ein Überblick über den Ablauf des SQLTE-Übersetzers gegeben. Nachdem im vorangegangenen Abschnitt die innerhalb der Datenbank benötigten Strukturen vorgestellt wurden, erfolgt nun die Beschreibung des eigentlichen Übersetzungsprogramms außerhalb der Datenbank. Dieses ist in der Sprache C entwickelt und mit Hilfe des Oracle Präprozessors Pro*C/C++ Release 8.1.5 sowie des GNU C Compilers GCC Version 2 kompiliert worden. Abbildung 5.2 zeigt den Abhängigkeitsgraph der Module. Wie üblich bezeichnet dort ein Pfeil von Modul A nach Modul B die Beziehung Modul A verwendet Funktion von Modul B. Die Module Eingabe und Ausgabe sind in der Darstellung ihrerseits unterteilt, um die Zusammenhänge etwas zu präzisieren. Auf diese Unterteilung wird im Abschnitt 5.3.2 näher eingegangen. Nachfolgend werden nun zunächst global verwendete Definitionen und Variablen vorgestellt, um dann die Funktionsweise der einzelnen Module zu erläutern. Diese Erläuterungen beschränken 68 KAPITEL 5. IMPLEMENTATION sich auf die wichtigsten Aufgaben der Module, eine umfassende Auflistung aller verwendeten Funktionen und Variablen ist in Anhang B zu finden. 5.3.1 Globale Strukturen Die Datei sqlte.h nimmt alle globalen Definitionen auf, d. h. sie enthält modulübergreifende Konstanten, Typen, Variablen und Funktionen. Die wichtigste globale Datenstruktur ist ohne Zweifel der Parserbaum. Dieser wird durch den Parser während der lexikalischen Analyse aufgebaut und enthält die vom Benutzer eingegebenen Symbole. In den Knoten des Baumes werden diese Symbole gemäß der Syntaxgraphen aus Anhang A zu größeren syntaktischen Einheiten zusammengefaßt. Die Übersetzung kann dann rekursiv mit einer Übersetzungsfunktion je Knotenart erfolgen. Der Baum wird mit Hilfe der Typen tdKnoten, tdSymbol und der globalen Variable pWurzel wie folgt implementiert. typedef struct sKnoten { struct sSymbol { WORD wSymbol; char szText[STRINGLAENGE]; WORD wTyp; WORD wZeile; WORD wSpalte; struct sKnoten* pWeiter; struct sKnoten* pTimeflag; } s[MAXSYM]; WORD nSymbole; WORD wKnoten; WORD wTyp; } tdKnoten; typedef struct sSymbol tdSymbol; tdKnoten* pWurzel; Wie man der Definition entnehmen kann, enthält jeder Knoten höchstens MAXSYM Symbole, die aktuelle Anzahl der Symbole nimmt die Variable nSymbole auf. In wKnoten wird die Art des Knotens gemäß der Syntaxgraphen aus Anhang A gespeichert, dazu existieren die globalen Konstanten beginnend mit ntk (z. B. ntkSTATEMENT, ntkDML, ntkQUERY). Zusätzliche Informationen zur Übersetzung der Knoten können in wTyp abgelegt werden, so z. B. welche Art von temporaler Unterstützung gewünscht ist. Dazu dienen die globalen Konstanten, die mit typ beginnen (z. B. typTEMPORAL, typKOMMANDO, typSEQUENCED). Die Baumstruktur wird in Abschnitt 5.3.5 anhand eines Beispiels näher erläutert. Jedes Symbol innerhalb eines Knotens verweist durch den Zeiger pWeiter entweder zu einem weiterem Knoten, oder es enthält Informationen zu dem entsprechenden Symbol. Die Verwendung der weiteren Variablen des Typs tdSymbol wird in den jeweiligen Modulen näher erläutert. Hier sei nur ein kurzer Überblick gegeben: wSymbol nimmt für jedes Terminalsymbol eine über Konstanten festgelegte Ganzzahl auf (z. B. tkSELECT, tkFROM, tkWHERE — alle hier verwendeten Konstanten beginnen mit tk). In den Variablen wZeile und wSpalte wird die Position des 5.3. PROGRAMMBESCHREIBUNG 69 Symbols in der Benutzereingabe abgelegt, um im Modul Parser aussagekräftige Fehlermeldungen generieren zu können. Die Zeichenkette szText speichert die Benutzereingabe des betreffenden Symbols, benötigt wird diese weniger für terminale Symbole als für Identifier und Konstanten. Der Zeiger pTimeflag verweist bei Bedarf auf einen anderen Knoten des Baumes, nämlich auf einen für die Übersetzung relevanten Knoten der Art ntkTIMEFLAG. In wTyp werden schließlich, ebenso wie in wTyp bei Knoten, mit Hilfe von bitweisen oder-Verknüpfungen von Konstanten verschiedene Zusatzinformationen zum Symbol gesammelt (z. B. typNONTEMP, typTABLEID, typINLINEVIEW — alle hier verwendeten Konstanten beginnen mit typ). Im Zusammenhang mit dem Aufbau des Parserbaumes existiert eine weitere globale Variable, namentlich der Zeiger pSymbol, der stets auf das zuletzt gescannte Symbol verweist. Schließlich ist pSQL92 als dritte und letzte globale Variable für die Aufnahme der übersetzten Anweisungen zuständig. Sie stellt als Zeiger vom Typ tdKommando eine verkettete Liste dar und ermöglicht so die Aufnahme einer dynamischen Anzahl von Zeichenketten der Länge KOMMANDOLAENGE. Diese werden i. d. R. nach erfolgreicher Übersetzung in der vorgegebenen Reihenfolge ausgeführt. Eine Ausnahme kann dabei die Anlage von temporären Tabellen bilden, bei denen die Ausführung schon während der Übersetzung stattfindet und die Variable bSofort zum Einsatz kommt. Die Definition geschieht wie folgt. typedef struct char BOOL struct sKom* } tdKommando; sKom { szText[KOMMANDOLAENGE]; bSofort; pWeiter; tdKommando* pSQL92; Wie eingangs erwähnt, werden die Prototypen der globalen Funktionen ebenfalls in der Datei sqlte.h deklariert. Diese Deklarationen sind sortiert nach den Modulen, in denen die zugehörigen Definitionen erfolgen. Die Beschreibung der globalen Funktionen befindet sich jeweils in den nachfolgenden Abschnitten, eine Übersicht liefert Tabelle 5.1. 5.3.2 Ein- und Ausgabeverarbeitung Die Ein- und Ausgabeverarbeitung des SQLTE-Übersetzers erfolgt durch die beiden Module Eingabe und Ausgabe. Wie bereits in Abbildung 5.2 zu sehen ist, lassen sich beide Module etwas feiner unterteilen, nämlich Eingabe in Kommando und Anmeldung sowie Ausgabe in Info, Prompt, Fehler und Ergebnisse. Diese Unterteilung wird innerhalb der Module durch die Namensgebung der globalen Funktionen berücksichtigt, d. h. für die Ausgabe existieren die Funktionen ZeigeInfo, ZeigePrompt, ZeigeErgebnis, ZeigeFehler, und für die Eingabe stehen KommandoZeichen, KommandoLeer sowie AnmeldungZeichen zur Verfügung. Weiterhin existieren die globalen Funktionen InitEingabe, InitAusgabe und KommandoReInit. Diese inhaltliche Trennung der Eingabequellen und Ausgabeziele ermöglicht eine einfache Umsetzung einer anspruchsvollen Benutzeroberfläche. In der vorliegenden prototypischen Implementation wird von dieser Möglichkeit zunächst kein Gebrauch gemacht. Stattdessen erfolgt die Ein- und Ausgabe über die Standardgeräte stdin und stdout, wie von SQL*Plus gewohnt. Durch die Kapselung der Ein- und Ausgabefunktionen in eigenen Modulen und die beschriebene 70 KAPITEL 5. IMPLEMENTATION Modul Funktionsprototyp Ausgabe void void void void void void char BOOL void char BOOL WORD WORD WORD WORD BOOL void void BOOL BOOL BOOL BOOL BOOL BOOL void BOOL Eingabe Scanner Parser CodeGen Oracle InitAusgabe(FILE *); ZeigeInfo(char *); ZeigeFehler(char *); ZeigeErgebnis(char *); ZeigePrompt(void); InitEingabe(FILE *); KommandoZeichen(void); KommandoLeer(void); KommandoReInit(void); AnmeldungZeichen(void); ScannerInit(void); Scanner(void); Text2Symbol(char *); Symbol2Text(char *); Parser(void); CodeGen(void); sqlConnect(char *, char *); sqlDisconnect(void); sqlExecute(char *); sqlCommit(void); sqlTEexists(void); sqlTableTemporal(char *); sqlTableExists(char *); sqlStrcatColumns(char *, char *); sqlDropTempTab(void); sqlGetSysdate(char *); Tabelle 5.1: Übersicht der globalen Funktionen 5.3. PROGRAMMBESCHREIBUNG 71 Unterteilung dieser Module sollte es jedoch leicht möglich sein, später etwa einen unabhängigen Anmeldedialog und ein mehrfach unterteiltes Ausgabefenster hinzuzufügen. 5.3.3 Hauptprogramm Das Modul SQLTE enthält das Hauptprogramm und initiiert damit alle weiteren Funktionen (s. a. Abbildung 5.1 und 5.2). Zunächst erfolgt die Initialisierung von Eingabe und Ausgabe, die Anmeldung an die Datenbank und die Überprüfung auf vorhandene SQLTE-Datenbankstrukturen. Nach Ausgabe des Kommandoeingabeprompts wird der Parser aufgerufen. Ist dieser erfolgreich, werden die eingegebenen Kommandos übersetzt und nach fehlerfreier Übersetzung ausgeführt. Wird kein Programmende gewünscht, wiederholt sich der beschriebene Ablauf durch Anzeigen des Kommandoeingabeprompts. Die dabei zur Verfügung gestellte Benutzeroberfläche ist wegen des prototypischen Charakters der Implementation relativ einfach und an SQL*Plus angelehnt (s. a. Abschnitt 5.3.2). Der Benutzer gibt nach der Anmeldung an die Oracle-Datenbank beim Eingabeprompt SQLTE> das temporale Kommando gemäß der Syntax in Anhang A ein und übergibt dieses durch Eingabe von EOF (end of file — die Tastatureingabe erfolgt häufig mit Ctrl-D) dem Programm. Die ausschließliche Eingabe von EOF verursacht die Beendigung des Programms. 5.3.4 Lexikalische Analyse Das Modul Scanner stellt die globalen Funktionen ScannerInit, Scanner, Text2Symbol und Symbol2Text zur Verfügung. Die beiden letztgenannten Funktionen ermöglichen mit Hilfe der nachfolgend definierten lokalen Datenstruktur die Zuordnung der Benutzereingaben in Textform zu den Konstanten der terminalen Symbole der Sprache und umgekehrt. Dabei ist vor allem die Übersetzungsrichtung der Funktion Text2Symbol hilfreich, da durch sie jede gescannte Benutzereingabe einmalig in eine Symbolkonstante umgewandelt wird, so daß im weiteren Programmverlauf beispielsweise Vergleiche bequem durch Konstanten ausgeführt werden können. Die Funktion der Rückübersetzung von Symbol2Text wird für die Generierung von Fehlermeldungen verwendet, um darauf hinzuweisen, daß einzelne Symbole an bestimmten Eingabepositionen erwartet werden. static struct { char szText[STRINGLAENGE]; WORD wKonstante; } sTerminal[] = { "NONSEQUENCED", "VALIDTIME", "SELECT", "INSERT", "DELETE", "UPDATE", ";", "(", ")", (...) tkNONSEQUENCED, tkVALIDTIME, tkSELECT, tkINSERT, tkDELETE, tkUPDATE, tkSEMICOLON, tkLRBRACKET, tkRRBRACKET, 72 KAPITEL 5. IMPLEMENTATION "IS", STOPMRK, tkIS, -1 }; Die Funktion ScannerInit besorgt durch Belegen lokaler Variablen und Einlesen des ersten Eingabezeichens die Initialisierung des Scanners und wird je Benutzereingabe einmal durch das Modul Parser aufgerufen. Das eigentliche Einlesen der Benutzereingaben übernimmt die Funktion Scanner. Diese wird aus dem Modul Parser aufgerufen und verlangt als Parameter einen Zeiger auf den aktuellen Knoten des Parserbaums. Die Funktion nimmt die nächsten Eingabezeichen entgegen und faßt diese zu gültigen Symbolen der Sprache zusammen. Dabei werden die gesammelten Informationen — namentlich Symbolkonstante, Texteingabe sowie Zeile und Spalte des Symbols in der Benutzereingabe — lokal in einer Variable vom Typ tdSymbol zwischengespeichert. Beim nächsten Aufruf von Scanner werden diese Symbolinformationen schließlich im aktuellen Knoten des Parserbaumes abgelegt. Der Symboleintrag im Parserbaum erfolgt also stets um ein Symbol zeitverzögert, so daß der Parser das aktuelle Symbol zunächst analysieren kann und ggf. eine Verzweigung im Parserbaum vornehmen kann, ehe das Symbol abgespeichert wird. Der Eintrag des letzten gültigen Symbols erfolgt durch den letzten Aufruf des Scanners, bei dem das Pseudosymbol tkEMPTY gelesen wird. Der Scanner verwendet die drei lokalen Hilfsfunktionen leseLeer, IdentifierTest und ScannerFehler. Die erste wird zum Überlesen syntaktisch irrelevanter Eingabezeichen (whitespaces — Tabulatoren, Leerzeichen und Zeilenvorschübe) verwendet. Die Funktion IdentifierTest entscheidet bei einer Benutzereingabe, die keiner Symbolkonstante zugeordnet werden kann, um welche Art von Eingabe es sich handelt, d. h. sie liefert eine der Konstanten tkIDENTIFIER, tkINTEGERVAL, tkFLOATVAL oder tkSTRINGVAL. Die Funktion ScannerFehler gibt schließlich Fehlermeldungen aus. Es handelt sich dabei um interne Fehler, etwa zuviele Symbole je Knoten oder zu lange Identifier, so daß diese nicht im Rahmen der syntaktischen Fehlermeldungen des Parsers generiert werden. 5.3.5 Syntaktische Analyse Die einzige globale Funktion des Moduls Parser ist die namensgleiche Funktion Parser. Diese wird vom Hauptprogramm aus einmal je Benutzereingabe aufgerufen. Sie erhält keine Parameter und baut ausgehend von der globalen Variable pWurzel den Parserbaum zur Benutzereingabe auf. Dabei wird nach dem Prinzip des rekursiven Abstiegs (recursive descent) die syntaktische Korrektheit der Eingabe gemäß der Syntaxgraphen aus Anhang A überprüft. In Abbildung 5.3 ist die schematische Darstellung eines Parserbaums am Beispiel der untenstehenden Anfrage aus Abschnitt 3.2.2 zu sehen. Dargestellt sind dort die Anzahl der Symbole je Knoten, die Knotenart und die Klartexte der Symbole — man vergleiche hierzu die Definition der zugehörigen Datenstrukturen in Abschnitt 5.3.1. select * from ( nonsequenced validtime select * from Ang a where validtime(a) contains period [’05.10.1999’, now)); 5.3. PROGRAMMBESCHREIBUNG 2 ntkSTATEMENT 73 ; 1 ntkQUERY 7 ntkSFW select * from ( 2 ntkTIMEFLAG nonsequenced validtime ) 7 ntkSFW select * from ang a where 3 ntkCONDEXP 4 ntkSCALAREXP validtime ( a ) 1 ntkCONDOP contains 6 ntkSCALAREXP period [ , ... ) ... Abbildung 5.3: Schematische Darstellung eines Parserbaums Für den eigentlichen rekursiven Abstieg werden die folgenden lokalen Funktionen verwendet, die alle mit einem Zeiger auf den aktuellen Knoten des Parserbaums als Parameter aufgerufen werden. Der Aufbau dieser Funktionen ist dabei prinzipiell identisch: Zu Beginn wird eine Verzweigung des Knotens vorgenommen, so daß jede Funktion genau einem Syntaxgraphen und einer ntk-Konstante entspricht. Mit Hilfe dieser Konstante wird nach der Verzweigung des Knotens die Knotenart abgelegt. Dann erfolgt das eigentliche Parsen, d. h. das aktuelle Token wird interpretiert und entweder akzeptiert oder durch eine Fehlermeldung abgelehnt. Weiterhin wird mit Hilfe der globalen Funktion Scanner das nächste Token entnommen und so fort. Dabei liest jede der Funktionen zum Abschluß ein Token und analysiert dieses nicht, so daß zu Beginn jeder Parserfunktion stets von der Existenz eines neuen Token ausgegangen wird. Das aktuelle Token wird in der lokalen Variable wToken abgelegt. void void void void void void void void void void void pStatement(void); pTimeflag(tdKnoten*); pQuery(tdKnoten*); pSFW(tdKnoten*); pDML(tdKnoten*); pDDL(tdKnoten*); pDatatype(tdKnoten*); pCondExp(tdKnoten*); pCondOp(tdKnoten*); pScalarExp(tdKnoten*); pTimestamp(tdKnoten*); 74 KAPITEL 5. IMPLEMENTATION void pConstant(tdKnoten*); Wie der obigen Definition zu entnehmen ist, weicht die Funktion pStatement etwas von dem beschriebenen Schema der Parserfunktionen ab, da diese den rekursiven Abstieg initiiert. Sie besitzt deshalb keinen Parameter und nimmt statt der Verzweigung des Knotens die Initialisierung der globalen Variable pWurzel vor. Außerdem ist es hier notwendig, zu Beginn der Funktion ein Token einzulesen. Einige Parserfunktionen besitzen eine etwas erweiterte Funktionalität, um Zusatzinformationen für die spätere Übersetzung abzulegen. So werden z. B. in der Funktion pTimeflag ein Zeiger auf eben diesen Timeflag-Knoten für das erste Symbol im nachfolgenden Knoten erzeugt und der Typ des nachfolgenden Kommandos abgelegt (typSEQ oder typNONSEQ). Auch die Konstanten typINLINEVIEW, typTABLEID, typJOIN, typTEMP und typNONTEMP werden zur Kennzeichung von Symbolen oder Knoten verwendet. Auf deren Einsatz wird in Abschnitt 5.3.6 näher eingegangen, er dürfte sich aber auch durch die Namensgebung der Konstanten erklären: typTABLEID bezeichnet ein Symbol, das eine Tabelle identizifiert, typINLINEVIEW wird für Symbole mit Verweis auf eine Unteranfrage verwendet. Die Konstante typJOIN zeichnet eine Anfrage als verbundbildend aus, während typTEMP und typNONTEMP sowohl für Symbole als auch für Knoten eingesetzt werden, um auf temporale Funktionalität hinzuweisen. Über die Parserfunktionen hinaus finden die vier lokalen Hilfsfunktionen ParserFehler, neuerKnoten, verzweigeKnoten und loescheKnoten Verwendung. Die Funktion ParserFehler generiert eine Fehlermeldung entweder mit Ausgabe einer Zeichenkette oder dem Hinweis auf ein fehlendes Symbol, je nachdem, ob sie mit einer Fehlernummer oder einer Symbolkonstante als Parameter aufgerufen wird. Durch die Verwendung der lokalen booleschen Variable bFehler wird nur der erste syntaktische Fehler angezeigt. Die Funktionalität der drei o. g. Funktionen zur Knotenverwaltung ist nicht sehr umfangreich und dürfte sich aus deren Bezeichnungen schließen lassen. 5.3.6 Semantische Analyse und Übersetzung Das Modul CodeGen übernimmt die Übersetzung der Benutzereingabe in eine oder mehrere SQL-Anweisungen, die vom zugrundeliegenden DBMS in einer Transaktion ausfgeführt werden. Zur Erläuterung des Moduls sollen nachfolgend zunächst allgemein die Aufgaben der einzelnen Funktionen vorgestellt werden, um anschließend mit Hilfe der Übersetzungsschemata der verschieden Knotenarten in Tabelle 5.2 bis 5.7 näher auf die Details der Übersetzung eingehen zu können. Modulbeschreibung Die einzige globale Funktion des Moduls ist CodeGen, welche die zur Übersetzung des Parserbaums notwendigen Initialisierungen vornimmt, durch ErgaenzeKnoten dem Parserbaum semantische Informationen hinzufügt und den rekursiv ausgeführten Übersetzungsvorgang durch die lokale Funktion tStatement initiiert. Die lokale Funktion ErgaenzeKnoten beinhaltet dabei einen Teil der semantischen Analyse, die sich im wesentlichen auf die temporalen Erweiterungen bezieht, da viele Fehlermeldungen — z. B. fehlerhafte Spaltenbezeichnungen — nach der Übersetzung von Oracle generiert werden. Diese Ergänzung wird ebenfalls rekursiv je Knoten vorgenommen. Es werden Tabellen und 5.3. PROGRAMMBESCHREIBUNG 75 Anfragen auf den Grad ihrer temporalen Funktionalität hin überprüft und die zugehörigen Knoten entsprechend klassifiziert. Dazu werden die Konstanten typTEMP, typNONTEMP, typUC und typTUC vergeben, die zusammen mit den Typinformationen aus dem Parservorgang typSEQ und typNONSEQ den späteren Übersetzungsvorgang vereinfachen. Bei Typunverträglichkeiten oder bei Anwendung noch nicht unterstützter Funktionalität (z. B. nonsequenced-Konstrukte) werden entsprechende Fehlermeldungen generiert und die Übersetzung abgelehnt. Weiterhin kommen die folgenden lokalen Funktionen zum Einsatz. Bei den letzten beiden handelt es sich um Hilfsfunktionen zur Verwaltung der verketteten Listenstruktur vom Typ tdKommando, die über die globale Variable pSQL92 die Übersetzungsergebnisse aufnimmt. Die sieben Funktionen tQuery bis tCondExp übernehmen die eigentliche Übersetzung und haben jeweils die Aufgabe, ihrem Namen zugehörige Knotenarten zu übersetzen. Für Knoten wie z. B. der Art ntkDATATYPE oder ntkCONSTANT, die keine Übersetzungsfunkton besitzen, findet derzeit noch keine Übersetzung statt, d. h. der Inhalt der Knoten wird unverändert in die SQL-Anweisungen übernommen. Die Funktionsweise der einzelnen Übersetzungsfunktionen wird später in diesem Abschnitt anhand der Übersetzungsschemata erläutert. BOOL BOOL BOOL BOOL BOOL BOOL BOOL BOOL void void Klartext(tdSymbol**, char *, tdSymbol*, WORD, WORD); tQuery(tdKnoten*, char*); tDDL(tdKnoten*, char*); tDML(tdKnoten*, char*); tSFW(tdKnoten*, char*); tTimestamp(tdKnoten*, char*); tTimeflag(tdKnoten*, char*); tCondExp(tdKnoten*, char*); loescheKommando(tdKommando* p); neuesKommando(char*, BOOL); Die lokale Funktion Klartext übernimmt die Koordination der Übersetzungen. Dazu werden sequentiell, beginnend bei einem gegebenen Symbol eines festen Knotens, alle Symbole übersetzt, bis die Abbruchbedingung erfüllt ist, d. h. eine bestimmte Anzahl von Symbolen durchlaufen wurde oder ein gesuchtes Symbol erreicht wurde. Dabei werden Symbole ohne Verzweigung im Klartext kopiert und an die gegebene Zeichenkette angehängt. Bei Symbolen mit Verzweigung wird der zugehörige Nachfolgerknoten übersetzt und zwar wiederum mit der Funktion Klartext bzw. mit einer spezifischen Übersetzungsfunktion, falls zu der Knotenart eine solche existiert. Als Rückgabewert wird stets der Erfolg der vorgenommen Übersetzung in Form einer booleschen Variable geliefert. Verwendet man also die Funktion Klartext für den Wurzelknoten, so wird der gesamte Inhalt des Parserbaums unter Berücksichtigung spezifischer Übersetzungsfunktionen rekursiv übersetzt. Man könnte daher als Beispiel die o. g. Funktion tStatement zur Initiierung des Übersetzungsvorgangs nur durch die eine folgende Anweisung formulieren. Tatsächlich bedarf es hier spezifischer Übersetzungen, weshalb die alleinige Anwendung der Funktion Klartext nicht ausreicht (s. a. Abschnitt 5.3.6). return(Klartext(NULL, szKommando, (*pWurzel).s, (*pWurzel).nSymbole-1, 0)); Der Wurzelknoten würde von seinem ersten Symbol an ((*pWurzel).s) bis zum Erreichen des vorletzten Symbols ((*pWurzel).nSymbole-1 — ohne das obligatorische abschließende Se- 76 KAPITEL 5. IMPLEMENTATION Bedingung SQLTE =⇒ SQL92 tkDROP und typUC tkDROP und typTUC drop table <tab> =⇒ drop table <tab> drop table <tab> =⇒ drop table <tab> drop view <tab> tuc create <tab> ({<col> <ntkDATATYPE> [,]}) =⇒ create <tab> ({<col> <datatype> [,]}) create <tab> ({<col> <ntkDATATYPE> [,]}) as validtime =⇒ create <tab> (rwo nr number, ts t stamp type, {<col> <ntkDATATYPE> [,]}) create view <tab> tuc as select {<col> [,]} from <tab> t where t.ts .tp intersection(sysdate) is not null tkCREATE und typNONTEMP tkCREATE und typTEMP Tabelle 5.2: Übersetzungsschema für ntkDDL-Knoten mikolon) übersetzt. Die Übersetzung würde dabei, neben mittels neuesKommando dynamisch generierter zusätzlicher Befehle, an die Zeichenkette szKommando angehängt. Übersetzung Wie soeben erläutert, befindet sich die eigentliche Funktionalität zur Übersetzung in den Funktionen tQuery bis tCondExp. Die Vorgehensweise in diesen Funktionen basiert auf den Übersetzungsschemata, die in den Tabellen 5.2 bis 5.7 dargestellt sind. Diese Darstellung erfolgt in leicht formalisierter Weise. Wenngleich diese Formalisierung — etwa im Vergleich zur erweiterten Backus-Naur-Form (EBNF) — etwas abkürzend und damit unpräzise ist, dürfte sie zusammen mit den Syntaxgraphen aus Anhang A gut verständlich sein. Es kommen dabei, neben den terminalen Symbolen der Sprache, spitze, eckige und geschweifte Klammern vor, um Platzhalter, Optionen und Mehrfachvorkommen darzustellen. Der senkrechte Strich wird wie üblich für die exklusive Auswahl zwischen verschiedenen Varianten verwendet. Terminale Symbole werden ohne kennzeichnende Anführungszeichen dargestellt, so daß runde Klammern nur als terminale Symbole vorkommen. Die Darstellung erfolgt stets der Art sqlte =⇒ sql92. Erscheinen auf der SQL92-Seite nicht übersetzte Knoten in spitzen Klammern (z. B. <ntkTIMEFLAG>), so ist die zugehörige Übersetzung des Knotens gemeint. Das Ergebnis der Übersetzung ist häufig mehrzeilig, es stellt dann eine mehrere Kommandos umfassende Transaktion dar. Diese sind durch entsprechende Zeileneinrückungen gekennzeichnet, die Ausführung erfolgt in der dargestellten Reihenfolge. Das letzte Kommando wird von den Übersetzungsfunktionen direkt an die angegebene Zeichenkette angehängt, alle vorherigen Befehle erhalten dynamisch Plätze in der Kommandoliste (s. a. Modulbeschreibung zu Beginn dieses Abschnitts). Kommen in der Übersetzung temporäre Tabellen zum Einsatz1 , so werden diese mit einem Index versehen, d. h. sqlte tempj meint die nächste noch nicht belegte Tabelle dieser Art. 1 Temporäre Tabellen müssen hier nicht notwendiger Weise temporäre Tabellen im Sinne von Oracle, d. h. durch create global temporary table angelegt, sein. Auch gewöhnliche Tabellen werden in dem Sinne temporär verwendet, daß sie nach der Transaktion wieder entfernt werden. Das liegt daran, daß Oracle-temporäre Tabellen wegen ihres Unvermögens, benutzerdefinierte Typen aufzunehmen, nicht die benötigte Flexibilität mitbringen. tkDELETE und typSEQ tkDELETE und typTUC tkDELETE und typUC tkUPDATE und typSEQ tkUPDATE und typUC Tabelle 5.3: Übersetzungsschema für ntkDML-Knoten insert into <tab> [({<col> [,]})] values ({<constant> [,]}) =⇒ insert into <tab> [({<col> [,]})] values ({<ntkCONSTANT> [,]}) [<timeflag>] insert into <tab> [({<col> [,]})] values ({<constant> [,]}) =⇒ insert into <tab> [(rwo nr , ts , {<col> [,]})] values (rwo nr te .nextval, t stamp type(t element tabl(<ntkTIMEFLAG>)), {<ntkCONSTANT> [,]}) update <tab> set {<col> = <scalarexp> [,]} [where <condexp>] =⇒ update <tab> set {<col> = <ntkSCALAREXP> [,]} [where <ntkCONDEXP>] <timeflag> update <tab> set {<col> = <scalarexp> [,]} [where <condexp>] =⇒ insert into sqlte rowidtempi (select rowid from <tab> t where t.ts .ti intersection(<ntkTIMEFLAG>) is not null [and (<ntkCONDEXP>)]) create table sqlte tempj nested table ts .ts store as sqlte tempj ntabl as (select * from <tab> t where rowid in (select tupel from sqlte rowidtempi )) update <tab> t set ts = t.ts .ti difference(t interval type(<ntkTIMEFLAG>)) where rowid in (select tupel from sqlte rowidtempi ) delete from <tab> where ts is null update sqlte tempj t set t.ts = t.ts .ti intersection(t interval type(<ntkTIMEFLAG>)), {<col> = <scalarexp> [,]} insert into <tab> (select * from sqlte tempj ) delete from table <tab> [where <condexp>] =⇒ delete from table <tab> [where <ntkCONDEXP>] delete from table <tab> [where <condexp>] =⇒ update <tab> t set t.ts = t.ts .ti difference(<ntkTIMEFLAG>) where t.ts .tp intersection(sysdate) is not null [and (<ntkCONDEXP>)] delete from <tab> where ts is null <timeflag> delete from table <tab> [where <condexp>] =⇒ update <tab> t set t.ts = t.ts .ti difference(<ntkTIMEFLAG>) where t.ts .ti intersection(<ntkTIMEFLAG>) is not null [and (<ntkCONDEXP>)] delete from <tab> where ts is null tkINSERT und typUC tkINSERT und (typTUC oder typSEQ) SQLTE =⇒ SQL92 Bedingung 5.3. PROGRAMMBESCHREIBUNG 77 typSEQ und typJOIN typSEQ und nicht typJOIN Tabelle 5.4: Übersetzungsschema für ntkSFW-Knoten select * | {<scalarexp> [,]} from {(<sfw>) | <tab> [<alias>] [,]} [where <condexp>] =⇒ select * | {<ntkSCALAREXP> [,]} from {(<ntkSFW>) | <tab> [<alias>] [,]} [where <ntkCONDEXP>] select * | {<scalarexp> [,]} from {(<timeflag> <sfw>) | <tab> [<alias>] [,]} [where <condexp>] =⇒ select * | {<ntkSCALAREXP> [,]} from {(select {<col> [,]} from <ntkSFW>temp t where t.ts .tp intersection(sysdate) is not null) | <tab> tuc [<alias>] [,]} [where <ntkCONDEXP>] <timeflag> select * | {<scalarexp> [,]} from (<timeflag> <sfw>) | <tab> [<alias>] [where <condexp>] =⇒ create table sqlte tempi nested table ts .ts store as sqlte tempi ntabl as (select * | rwo nr , ts , {<ntkSCALAREXP> [,]} from <ntkSFW>temp | <tab> <alias> where [<ntkCONDEXP> and] (<alias>.ts .ti intersection(<ntkTIMEFLAG>) is not null)) sqlte tempi <timeflag> select * | {<scalarexp> [,]} from {(<timeflag> <sfw>) | <tab> [<alias>] [,]} [where <condexp>] =⇒ create table sqlte tempi nested table ts .ts store as sqlte tempi ntabl as (select {<alias>l .rwo nr as rwo nrl }, sqlte .te intersection({<alias>l .ts [,]}) as ts , {<alias>l .<col> as <alias>l <col> [,]} | {<ntkSCALAREXP> [,]} from {<ntkSFW>temp | <tab> <alias>l [,]} where [<ntkCONDEXP> and] ({<alias>l .ts .ti intersection(<ntkTIMEFLAG>) is not null and} sqlte .te intersection({<alias>l .ts [,]}) is not null)) create global temporary table sqlte tempj on commit delete rows as (select distinct {rwo nrl ,} NULL as newrwo nr from sqlte tempi ) update sqlte tempj set newrwo nr = rwo nr te .nextval create table sqlte tempk nested table ts .ts store as sqlte tempk ntabl as (select newrwo nr as rwo nr , ts , {<col> [,]} from sqlte tempi dt , sqlte tempj rt where {rt .rwo nrl = dt .rwo nrl [and]} sqlte tempk typUC typTUC SQLTE =⇒ SQL92 Bedingung 78 KAPITEL 5. IMPLEMENTATION 5.3. PROGRAMMBESCHREIBUNG 79 Wie schon zu Beginn dieses Abschnitts in der Modulbeschreibung erwähnt, werden Knoten ohne Übersetzungsfunktion im Klartext übernommen. Für diese Knoten existiert daher auch keine Übersetzungsvorschrift. Am Beispiel der Tabelle 5.2 liest sich eine Übersetzungsvorschrift wie folgt. Die createAnweisung für temporale Tabellen befindet sich in der letzten Zeile. In der ersten Anweisung der Übersetzung werden Tabellenname und Spaltenbezeichner wie auch die Datentypen übernommen, nur die Attribute rwo nr und ts zur Aufnahme des Schlüssels zur Tupelzuordnung und des Zeitstempels werden ergänzt. In der zweiten Anweisung wird eine Sicht mit dem Namen <tab> tuc angelegt, die für temporal aufwärtskompatible Anfragen stets die aktuellen Einträge einer temporalen Tabelle bereithält. In Tabelle 5.3 sind die Übersetzungsregeln für Knoten mit Datenmanipulationsanweisungen zu sehen. Für den insert-Befehl unterscheiden sich die TUC- und die SEQ-Variante nur durch die u. U. andere Interpretation von <nktTIMEFLAG> (s. a. Tabelle 5.7) und stehen daher zusammen in einer Zeile. Auf die Darstellung des update-Kommandos bei typTUC wurde aus Platzgründen verzichtet, da im Vergleich zur SEQ-Version nur eine andere Variante der temporalen Schnittbildung (tp intersection statt ti intersection) verwendet wird — beim delete-Befehlt ist der gleiche Unterschied zwischen TUC und SEQ gut ablesbar. Selektionsanweisungen werden gemäß Tabelle 5.4 übersetzt. Für temporal aufwärtskompatible Anfragen (typTUC) wurde dabei die im folgenden erläuterte, leicht vereinfachte Darstellung gewählt. Eine TUC-Anfrage zeichnet sich dadurch aus, daß wenigstens eine der verwendeten Tabellen oder Inline-Views temporal ist, nicht notwendiger Weise alle müssen temporal sein. In der Übersetzungsvorschrift wird nur von temporalen Tabellen und Inline-Views ausgegangen, d. h. es wird immer für den aktuellen Zeitpunkt ausgewertet. Tatsächlich werden nicht-temporale Tabellen und Inline-Views von dieser Behandlung ausgeschlossen und unverändert eingesetzt. Sequentielle Selektionsanweisungen produzieren als Übersetzung generell eine temporäre Tabelle, die das Anfrageergebnis enthält. Das gilt sowohl für die Übersetzungen von tQuery wie auch für die der Funktion tSFW. Wie man Tabelle 5.4 entnehmen kann, werden diese temporären Tabellen durch create table as erzeugt und jeweils als letztes Kommando an die aufrufende Funktion übergeben. Der Zugriff auf diese so übergebenen Tabellen ist in den Übersetzungsschemata dann z. B. durch <ntkSFW>temp dargestellt. Die Tabellen enthalten ebenso wie die explizit vom Benutzer angelegten temporalen Tabellen zwei zusätzliche Spalten zur Aufnahme der temporalen Informationen. Diese verbindliche Verwendung temporärer Tabellen hat unterschiedliche Gründe: Zum einen gibt es verschiedene unkalkulierbare Resultate von Oracle Version 8.1.5 bei der Verwendung von geschachtelten Anfragen in Zusammenhang mit benutzerdefinierten Typen (s. a. Abschnitt 4.1.4). Zum anderen vereinfacht sich das Auslesen von Attributnamen bei der Verwendung von expliziten Tabellen im Gegensatz zu geschachtelten Anfragen. Insbesondere der *-Operator läßt sich so mit deutlich weniger Aufwand unterstützen. Schließlich wird die Übersetzung einfacher nachvollziehbar, und auch die Fehlersuche wird durch die Materialisierung von Zwischenergebnissen vereinfacht. Dennoch scheint dieses nicht unbedingt die effektivste Lösung zu sein, so daß für nicht-temporale Ergebnisse im Bereich von UC- und TUC-Anfragen — da dort die o. g. Gründe nicht gelten — auf die obligatorische Verwendung von temporären Tabellen verzichtet wird. Tabelle 5.5 zeigt die Übersetzungsvorschrift für ntkSTATEMENT-Knoten. Dort werden nur Knoten der Art ntkQUERY explizit übersetzt, bei den anderen werden die Ergebnisse der entsprechenden Übersetzungsfunktionen unverändert übernommen. Diese Ausnahme für sequentielle Queries begründet sich durch die notwendige Umwandlung der objektwertigen Zeitstempel in 80 KAPITEL 5. IMPLEMENTATION Bedingung SQLTE =⇒ SQL92 ntkQUERY und nicht typSEQ ntkQUERY und typSEQ <query> =⇒ <ntkQUERY> <query> =⇒ select rwo nr , sqlte .ts2string(ts ) as ts , {<col> [,]} from <ntkQUERY>temp order by rwo nr , ts Tabelle 5.5: Übersetzungsschema für ntkSTATEMENT-Knoten Bedingung SQLTE =⇒ SQL92 tkUNION und nicht typSEQ tkUNION und typSEQ <sfw1> | <query1> union <sfw2> | <query2> =⇒ <ntkSFW1> | <ntkQUERY1> union <ntkSFW2> | <ntkQUERY2> <timeflag1> <sfw1> | <query1> union <timeflag2> <sfw2> | <query2> =⇒ create global temporary table sqlte tempi on commit delete rows as (select sqlte .nextval as rwo nr , {<col> [,]} from (select {<col> [,]} from <ntkSFW1>temp | <ntkQUERY1>temp union select {<col> [,]} from <ntkSFW2>temp | <ntkQUERY2>temp )) create table sqlte tempj nested table ts .ts store as sqlte tempj ntabl as (select u.rwo nr , v.ts , {u.<col> [,]} from sqlte tempi u, (select * from <ntkSFW1>temp | <ntkQUERY1>temp union all select * from <ntkSFW2>temp | <ntkQUERY2>temp ) v where {u.<col> = v.<col> [,]}) Tabelle 5.6: Übersetzungsschema für ntkQUERY-Knoten Zeichenketten für Anfragen in der obersten Stufe der Schachtelung, d. h. genau für solche, die in ntkSTATEMENT zu finden sind. Die Umwandlung wird mit Hilfe der Funktion ts2string vorgenommen (s. a. Abschnitt 5.2.3 und 5.3.7). Für die Übersetzung temporaler Mengenoperationen ist in Tabelle 5.6 das noch unvollständige Schema dargestellt. Differenz und Schnittbildung bleiben dort unberücksichtigt, es bedarf hier weiterer Entwicklungsarbeit. Man findet in [Ste98a] Algorithmen für temporale Mengenoperationen, jedoch lassen diese Duplikate unberücksichtigt. Damit ergibt sich ein Widerspruch zur Schnappschuß-Reduzierbarkeit auf nicht-temporale Mengenoperationen, bei denen Duplikate stets entfernt werden. Die Übersetzung in der Tabelle 5.6 sieht die temporale Vereinigung mit Duplikatelimination vor. Es werden dort zunächst alle nicht-temporalen Attribute ({<col>}) vereinigt und mit neuen rwo nr versehen, um diese dann den temporalen Tupeln durch Verbundbildung wieder hinzuzufügen. Auf diese Weise erhalten wertgleiche Tupel identische rwo nr , so daß bei der Ausgabe eine temporale Verschmelzung vorgenommen wird. Für die Übersetzung von Knoten der Art ntkCONDEXP, die WHERE-Klauseln aufnehmen, bleibt in der vorliegenden Arbeit leider keine Zeit. Diese Knoten werden derzeit identisch in die Über- 5.3. PROGRAMMBESCHREIBUNG Knotenart SQLTE =⇒ SQL92 ntkTIMESTAMP NOW =⇒ SYSDATE BEGINNING =⇒ ’01.01.-4712 00:00:00’ FOREVER =⇒ ’31.12.9999 23:59:59’ ’dateval’ =⇒ ’dateval’ [[nonsequenced] validtime] =⇒ t interval type(sysdate, ’31.12. 9999 23:59:59’) [nonsequenced] validtime period [<timestamp>, <timestamp>) =⇒ t interval type(<ntkTIMESTAMP>, <ntkTIMESTAMP>) ntkTIMEFLAG 81 Tabelle 5.7: Übersetzungsschema für ntkTIMESTAMP- und ntkTIMEFLAG-Knoten setzung übernommen, so daß bei temporalen Anfragen nur einfache WHERE-Klauseln korrekte Ergebnisse liefern. Diese Vereinfachung ist damit zu begründen, daß sich bei komplizierteren Konstrukten mit EXISTS oder IN die Übersetzung als etwas problematisch darstellt: Es muß stets die temporale Überlappung zwischen Haupt- und Unteranfrage sichergestellt, werden und dieser temporale Durchschnitt wird dann außerhalb des Gültigkeitsbereichs der Unteranfrage in der Hauptanfrage benötigt. In [TJB97] ist dieses Problem anhand eines Beispiels illustriert, leider wird dort kein konkreter Lösungsvorschlag geboten. Schließlich sind in Tabelle 5.7 die Übersetzungsregeln für ntkTIMESTAMP- und ntkTIMEFLAGKnoten dargestellt. Wie man dort sehen kann, werden explizite Datumsangaben unverändert übernommen, so daß auch Fragmente des fixen Formats ’TT.MM.±JJJJ ST:MI:SE’ anwendbar sind, sofern diese von Oracle richtig interpretiert werden können. Weiterhin ist für ntkTIMEFLAG zu bemerken, daß auch wenn dieses bei TUC-Anweisungen nicht vorhanden ist, eine Übersetzung erfolgt, nämlich in t interval type(sysdate, ’31.12. 9999 23:59:59’). Diese Vorgehensweise ist für die Übersetzung von TUC-Kommandos hilfreich und führt auch bei UCAnweisungen nicht zu fehlerhaften Ergebnissen, da die Funktion tTimeflag nur explizit bei Bedarf aufgerufen wird und nicht automatisch bei jedem Auftreten von ntkTIMEFLAG-Knoten. 5.3.7 Datenbankschnittstelle Das Modul Oracle stellt dem Übersetzungsprogramm Funktionen zur Kommunikation mit der Datenbank zur Verfügung. Für diesen Zugriff auf die Datenbank sind für C-Programme verschiedene Möglichkeiten vorgesehen (s. a. [Ora99g]). Diese sollen zunächst kurz vorgestellt werden, ehe sich die eigentliche Beschreibung des Moduls anschließt. Pro*C/C++ — Embedded SQL Wie der Name es bereits vermuten läßt, werden in dieser Variante direkt in den Quellcode spezielle Datenbankkommandos eingebettet. Es bedarf nicht der expliziten Verwendung von Bibliotheksfunktionen des Datenbankpakets, daher sind diese Kommandos relativ einfach zu handhaben. Der erweiterte C-Quellcode wird i. d. R. durch die Dateiextension .pc gekennzeichnet. Der Oracle Pro*C/C++ Precompiler übersetzt diese Dateien dann in C-Quelldateien mit der Dateiextension .c, indem gewöhnliche C-Konstrukte unberührt bleiben und die Datenbankkom- 82 KAPITEL 5. IMPLEMENTATION mandos in entsprechende Funktionsaufrufe der mitgelieferten Laufzeitbibliothek2 umgewandelt werden. Ein wesentlicher Teil der Kommunikation findet dabei über sogenannte Host-Variablen statt, die über Precompiler-Anweisungen definiert werden und sowohl vom C-Programm wie auch vom DBMS gelesen und geschrieben werden können. Die Sprache embedded SQL läßt sich zunächst in statisches und dynamisches SQL unterteilen, wobei die Anweisungenen von statischem SQL zur Zeit der Übersetzung bis auf den Inhalt evtl. beteiligter Host-Variablen festliegen. Diese Methode findet z. B. Verwendung, wenn das Datumsformat für die aktuelle Sitzung geändert werden soll, oder eine Anfrage an eine von vornherein bekannte Tabelle mit Hilfe einer Host-Variablen (hier TableName) gestellt wird: EXEC SQL ALTER SESSION SET NLS_DATE_FORMAT = ’DD.MM.SYYYY HH24:MI:SS’; EXEC SQL SELECT * FROM USER_NESTED_TABLES WHERE TABLE_NAME = :TableName; Bei dynamischem SQL handelt es sich um Datenbankkommandos, die zur Zeit der Übersetzung noch nicht feststehen und stattdessen erst zur Laufzeit durch Zeichenketten generiert werden. Dazu zählen im Falle des SQLTE-Übersetzers z. B. SQL-Anfragen, die wegen der beliebigen Anzahl der verwendeten Tabellen und Spalten nicht in statische Anfragen übersetzt werden können. Dynamisches SQL wird wiederum in vier verschiedene Methoden unterteilt, je nachdem, ob bei dem Datenbankzugriff Anfragen verwendet werden und ob die Anzahl der benötigten Host-Variablen von vornherein bekannt ist (s. [Ora99g]). Schon um nur beliebige Benutzeranfragen ohne temporale Funktionalität im Bereich der Aufwärtskompabilität ausführen zu können, bedarf es somit der vierten Methode, die sowohl Anfragen als auch eine freie Anzahl von Host-Variablen zuläßt. Von den o. g. einfachen statischen Zugriffen abgesehen, kommt daher im wesentlichen diese Methode zum Einsatz. Diese sogenannte vierte Methode wird ab der Version Oracle 8i wiederum in zwei Varianten angeboten, nämlich Oracle dynamic SQL und ANSI dynamic SQL. Dabei unterstützt nur die letztgenannte Variante die Verwendung von Objekttypen, so daß in der vorliegenden Arbeit nur diese zum Einsatz kommt. Die dafür benötigten Deklarationen sind relativ umfangreich, so daß an dieser Stelle auf ein Beispiel verzichtet werden soll. Einzelheiten zur Verwendung von ANSI dynamic SQL findet man in [Ora99g]. Oracle Call Interface — OCI Das Oracle Call Interface erlaubt die direkte Verwendung von Oracle-Bibliotheksfunktionen. Es stellt damit die low level -Schnittstelle für die Entwicklung von Datenbankanwendungen dar und kann nur zusammen mit der Programmiersprache C verwendet werden [Ora99h]. In der Version Oracle 8.0 ist das OCI die einzige Möglichkeit, um benutzerdefinierte Typen in dynamischen SQL-Anfragen mit variablen Tabellennamen zu verwenden. Durch ANSI dynamic SQL (s. o.) wird ab der Version 8i diese Funktion auch mit embedded SQL möglich. Das OCI ist im Gegensatz zu Pro*C/C++ nicht ANSI/ISO-verträglich, die zukünftige Unterstützung wird aber von Oracle garantiert. Die OCI-Funktionen sind auch in embedded SQL-Programmen verwendbar, d. h. beide Methoden können kombiniert werden. Diese Kombination ist z. B. bei der Verwendung von benutzerdefinierten Datentypen in embedded SQL-Programmen notwendig. Es werden dann in Form 2 namentlich SQLLIB bzw. libsql.a unter UNIX 5.3. PROGRAMMBESCHREIBUNG 83 von C-Strukturen vordefinierte OCI-Datentypen verwendet. Für die Darstellung von selbstdefinierten Objekttypen durch C-Strukturen liefert Oracle ein Werkzeug namens Object Type Translator (OTT) mit. So wird im Falle der für SQLTE verwendeten Typen (vgl. Abschnitt 4.2.1 und 5.2.2) durch die folgende Anweisung die dargestellte Datei oracle.h erzeugt, in der C-Äquivalente zu den Datenbanktypen auf OCI-Basis definiert werden. Der Object Type Translator verwendet hier die Textdatei in.typ, in der die zu übersetzenden Typen spezifiziert sind (in diesem Fall t interval type, t element tabl und t stamp type). Die Definitionen der Typen werden direkt aus dem angegebenen Datenbankschema gelesen. ott intype=in.typ outtype=out.typ hfile=oracle.h user=reinhard/sqlte@spatial code=c typedef OCIRef t_interval_type_ref; typedef OCITable t_element_tabl; typedef OCIRef t_stamp_type_ref; struct t_interval_type { OCIDate vt_begin; OCIDate vt_end; }; typedef struct t_interval_type t_interval_type; struct t_interval_type_ind { OCIInd _atomic; OCIInd vt_begin; OCIInd vt_end; }; typedef struct t_interval_type_ind t_interval_type_ind; struct t_stamp_type { t_element_tabl * ts; }; typedef struct t_stamp_type t_stamp_type; struct t_stamp_type_ind { OCIInd _atomic; OCIInd ts; }; typedef struct t_stamp_type_ind t_stamp_type_ind; Modulbeschreibung In dem Modul Oracle werden die folgenden globalen Funktionen definiert, die — mit Ausnahme von sqlExecute und sqlDropTempTab — mittels statischem embedded SQL mit dem 84 KAPITEL 5. IMPLEMENTATION DBMS kommunizieren. Für die An- und Abmeldung an die Datenbank sorgen die Funktionen sqlConnect und sqlDisconnect, dabei wird im Falle von Mißerfolg durch die lokale Fehlerprozedur eine Fehlermeldung generiert und das Programm beendet, während die anderen Funktionen mögliche Fehlerzustände per Rückgabewert übergeben. void void BOOL BOOL BOOL BOOL BOOL BOOL void BOOL sqlConnect(char *, char *); sqlDisconnect(void); sqlExecute(char *); sqlCommit(void); sqlTEexists(void); sqlTableTemporal(char *); sqlTableExists(char *); sqlStrcatColumns(char *, char *); sqlDropTempTab(void); sqlGetSysdate(char *); Durch sqlCommit werden die Änderungen der letzten Transaktion endgültig an die Datenbank übergeben. Bis auf sqlDropTempTab lesen die übrigen Funktionen zur Übersetzung benötigte Informationen aus dem Data Dictionary. Eine Beschreibung der einzelnen Funktionen findet sich in Anhang B. Weiterhin existiert die Funktion sqlExecute, die dynamisch SQL-Befehle mit Hilfe des oben beschriebenen ANSI dynamic SQL ausführt und zur Ausführung der übersetzten SQLTEKommandos verwendet wird. Zur Darstellung von Oracle-Fehlermeldungen und der Ausgabe von Anfrageergebnissen werden dabei die folgenden lokalen Funktionen verwendet. void void void void sqlError(void); ZeigeAnfrageErgebnis(void); ZeigeZeitstempel(char*, WORD); VerschmelzeZeitstempel(char*, char*, char*); Die Funktion sqlError wird bei der Anmeldung an die Datenbank mit Hilfe der nachfolgenden embedded SQL-Anweisung als lokale Fehlerprozedur installiert, so daß diese bei auftretenden Fehlern bzgl. der Datenbankkommunikation direkt aufgerufen wird und die entsprechende Oracle-Fehlermeldung anzeigt. EXEC SQL WHENEVER SQLERROR DO sqlError(); Die Funktion ZeigeAnfrageErgebnis übernimmt im Falle von dynamisch ausgeführten Anfragen die formatierte Darstellung der Ergebnisse. Diese Formatierung hat nur prototypischen Charakter, d. h. es werden feste Feldlängen verwendet, und es besteht für den Benutzer nicht die Möglichkeit, zusätzliche Formatierungen, wie etwa von SQL*Plus gewohnt, anzubringen. Die Funktion überprüft die vom DBMS rückgelieferten Daten auf die Spalten RWO NR sowie TS und stuft danach das Ergebnis ggf. als temporal ein. Temporale Ergebnisse werden mit Hilfe der Funktion ZeigeZeitstempel zusammen mit ihren Zeitstempeln dargestellt. Weiterhin erfolgt unter Verwendung der Funktion VerschmelzeZeitstempel bei identischer RWO NR und übriger Wertgleichheit in der Ausgabe eine Verschmelzung der Zeitstempel, so daß die nicht-temporalen 5.4. TESTDATENBANK 85 Daten dann nur einmalig dargestellt werden. Voraussetzung für diese temporale Vereinigung ist die nach RWO NR sortierte Ausgabe der Anfrageergebnisse, die im Rahmen der Übersetzung berücksichtigt wird (s. Abschnitt 5.3.6). Leider hat sich bei der Übergabe der objektwertigen Zeitstempel im Rahmen der Ergebnispräsentation das folgende Problem ergeben, so daß dort — um überhaupt temporale Informationen darstellen zu können — ein Provisorium hingenommen werden mußte. Wie zu Beginn dieses Abschnitts erläutert, ist die Verwendung von objektwertigen Typen über OCI-Datentypen vorgesehen. Die zugehörigen Definitionen (s. o.) und prinzipielle Verwendung dieser Strukturen bereitet auch keine Schwierigkeiten: In der Datei oracle.pc existiert dazu die Funktion Test2, die im Falle statischer Anfragen Zeitstempel entgegennimmt und auswertet. Für dynamische Anfragen zeigte dieses Vorgehen jedoch keinen Erfolg, wie man der Funktion Test entnehmen kann. Die Oracle-Dokumentation sieht einen solchen Zugriff zwar ausdrücklich vor, aber dessen Ausführbarkeit wird nicht anhand eines Beispiels demonstriert (s. [Ora99g]). Sicher ist alternativ das Auslesen von Objekten in der Datenbank über OCI-Funktionen machbar, jedoch erscheint für deren Anwendung eine Einarbeitungszeit vonnöten, die im Rahmen dieser Arbeit nicht mehr zur Verfügung steht. Es wurde daher — wie oben erwähnt — das folgende Provisorium geschaffen: Die zu übergebenden Zeitstempel werden mit Hilfe einer PL/SQL-Funktion (s. Abschnitt 5.2.3) vor der Übergabe durch Aufzählung der beteiligten Intervalle in eine Zeichenkette umgewandelt. Diese Zeichenkette wird wie die anderen nicht-temporalen Typen problemlos übergeben und für die Bildschirmausgabe entsprechend interpretiert. 5.4 Testdatenbank In diesem letzten Abschnitt soll nun umfangreiches Datenmaterial zur Verfügung gestellt werden, um die noch eingeschränkte temporale Funktionalität des Prototypen SQLTE mit realistischeren Anforderungen testen zu können, als dieses etwa mit der oft bemühten Beispieltabelle Ang möglich ist. Dazu werden mit den unten genannten Anweisungen Daten aus der Institutsbibliothek übernommen. create table dokument ( doknr number, titel varchar(20), verlag varchar(20), ort varchar(20)) as validtime; create table literf ( doknr number, lieferant varchar(10)) as validtime; insert into dokument ( select RWO_NR_TE_.NEXTVAL, t_stamp_type(t_element_tabl(t_interval_type( erfdatum, ’31.12.9999 23:59:59’))), doknr, substr(titel, 1, 20), substr(verlag, 1, 20), substr(ort, 1, 20) from dok); 9803 rows created. 86 KAPITEL 5. IMPLEMENTATION insert into literf ( select RWO_NR_TE_.NEXTVAL, t_stamp_type(t_element_tabl(t_interval_type( erfdatum, ’31.12.9999 23:59:59’))), doknr, lieferant from lit); 3379 rows created. Die beiden beschriebenen Tabellen werden in diesem Testszenario — ohne genaue Kenntnis des tatsächlichen Verwendungszwecks — als der Buchbestand der Bibliothek (Tabelle dokument) und der bereits inventarisierte Bestand (Tabelle literf) interpretiert. Die Gültigkeitszeit eines Dokuments beginnt also mit dem Eintreffen des Schriftstücks in der Bibliothek und die Gültigkeitszeit einer Literaturerfassung beginnt mit seiner korrekten Buchung zum Inventar. Welche Dokumente vom Lieferanten Teubner waren im Juli 97 eingetragen und auch inventarisiert? — Diese Anfrage liefert die folgenden Kommandos in SQL, SQLTE und in der übersetzten Form. Beim Vergleich der nicht-temporalen und der temporalen Variante muß natürlich berücksichtigt werden, daß die letztgenannte mehr Funktionalität enthält: Mit Hilfe der Gültigkeitszeit ist es nun möglich, z. B. technisch überholte Bücher wieder aus der Bibliothek zu entfernen, ohne die Informationen aus der Vergangengeit zu löschen. sql: select d.doknr from dok d, lit l where d.doknr=l.doknr and d.erfdatum <= ’31.07.1997’ and l.erfdatum <= ’31.07.1997’ and lieferant = ’Teubner’; sqlte: validtime period [’01.07.1997’, ’01.08.1997’) select d.doknr from dokument d, literf l where d.doknr = l.doknr and lieferant = ’Teubner’; ueb: create table sqlte_temp0_ nested table ts_.ts store as sqlte_temp0_ntabl_ as (select d.rwo_nr_ as rwo_nr0_, l.rwo_nr_ as rwo_nr1_, sqlte_.te_intersection(d.ts_, l.ts_) as ts_, d.doknr as ddoknr from dok d, lit l where lieferant = ’Teubner’ and (d.ts_.ti_intersection(...) is not null and l.ts_.ti_intersection(...) is not null and sqlte_.te_intersection(d.ts_, l.ts_) is not null)) create global temporary table sqlte_temp1_ on commit delete rows as (select distinct rwo_nr0_, rwo_nr1_, NULL as newrwo_nr_ from sqlte_temp0_) update sqlte_temp1_ set newrwo_nr_ = rwo_nr_te_.nextval create table sqlte_temp2_ nested table ts_.ts store as sqlte_temp2_ntabl_ as (select newrwo_nr_ as rwo_nr_, ts_, ddoknr from sqlte_temp0_ dt, sqlte_temp1_ rt where dt.rwo_nr0_ = rt.rwo_nr0_ and dt.rwo_nr1_ = rt.rwo_nr1_) Kapitel 6 Ausblick Wie allgemein für Arbeiten dieser Art üblich soll auch das vorliegende Werk mit einem Ausblick auf mögliche Erweiterungen und Verbesserungen schließen. Auf diese Weise wird einerseits die geleistete Arbeit klar eingegrenzt und andererseits deutlich gemacht, an welcher Stelle zukünftige Projekte aufsetzen können. Die aus Sicht des Autors denkbaren Neuerungen werden nachfolgend unterteilt in Erweiterungen der temporalen Anfragesprache SQLTE und der eigentlichen Implementierung des Übersetzungsprogramms. Weshalb beide Teile recht umfangreich ausfallen, soll vorab kurz erläutert werden. Zunächst einmal dürfte es unmittelbar einleuchten, daß eine temporale Erweiterung von SQL, die im Rahmen einer Diplomarbeit formuliert und realisiert wird, dem Vergleich mit der Funktionsvielfalt von SQL nicht standhalten kann und sich daher auf jeden Fall eine Fülle von Erweiterungsmöglichkeiten bieten. Darüber hinaus mußten im Verlauf der Arbeit weitere Einschränkungen vorgenommen werden. Das liegt für den praktischen Teil der Arbeit zum einen daran, daß eine Vielzahl von neuen Features des DBMS zum Einsatz gekommen sind, für die vorab erst wenige Erfahrungen gesammelt werden konnten. Der Wechsel auf die neue Version Oracle 8.1.5 erst während der Arbeit hat dabei den Umfang der neuen Möglichkeiten stärker vergrößert als zunächst angenommen. Zum anderen haben sich einige dieser neuen Eigenschaften als noch recht unzuverlässig und fehlerbehaftet herausgestellt. Obwohl durch die Auswahl des einfach geschachtelten Speichermodells objekt-relationale Funktionalität nur in eingeschränktem Umfang zur Anwendung kam, konnten einige Übersetzungen nicht in der geplanten Form, sondern nur mit Hilfe von syntaktischen Tricks, ausgeführt werden. Die von Oracle derzeit vorgesehene klare Trennung der object option vom bewährten und zuverlässigen relationalen Teil des Oracle DBMS erscheint in diesem Zusammenhang durchaus gerechtfertigt. Für die Formulierung der Sprache im theoretischen Teil der Arbeit hat die oben beschriebene Problematik bewirkt, daß praktische Tests erst spät und nur in eingeschränktem Umfang ausgeführt werden konnten, so daß Rückschlüsse auf die Praktikabilität von SQLTE leider fehlen. Insgesamt ergeben sich für den Sprachumfang von SQLTE folgende Erweiterungsmöglichkeiten. Generell wünschenswert ist eine vollständige Aufwärtskompatibilität zum nicht-temporalen SQL von Oracle. Dafür bedarf es z. B. der Unterstützung von Funktionen und insbesondere von Aggregationen sowie den Konstrukten group by, order by und having. Weiterhin sind hier die Formulierung von Sichten und Integritätsbedingungen zu nennen. Alle diese Forderungen sind auch auf die temporale Funktionalität der Sprache übertragbar, insbesondere fehlt hier die 87 88 KAPITEL 6. AUSBLICK Formulierbarkeit temporal invarianter Schlüssel. Bereits in Abschnitt 3.2 wurde auf die Möglichkeit hingewiesen, einen nicht-temporalen Datentyp timestamp einzuführen, der ein temporales Element umfaßt und als Rückgabewert des validtime-Operators Verwendung finden kann. Ebenso finden sich dort Vorschläge, die Datenmanipulationskommandos der Sprache zu erweitern, so daß Änderungen von Gültigkeitszeitbereichen möglich sind. Als ein weiteres Feld zukünftiger Erweiterungen ergibt sich die Unterstützung spezieller temporaler Integritätsbedingungen. Für die Realisierung von SQLTE in der hier vorgestellten Form sind als Ergänzungsmöglichkeiten an erster Stelle die nicht-sequentiellen Anfragen zu nennen. Für diese sind in Kapitel 3 bereits Syntax und Semantik entwickelt worden, und in Kapitel 4 finden sich Ansätze zur Übersetzung. Des weiteren wurde in Abschnitt 4.1 und 4.2 auf die Verwendungsmöglichkeiten von indexorganisierten Tabellen und Indexen zur effizienteren Auffindung temporaler Informationen hingewiesen. Darüber hinaus bedarf es auch für die sequentiellen Anfragen einer Vervollkommnung des Funktionsumfangs, in Abschnitt 5.3.6 wurde auf die unvollständigen Übersetzungsregeln für temporale Mengenoperationen und temporale Subqueries hingewiesen. Als unmittelbare Verbesserungsvorschläge für die Implementation ist eine objektwertige Datenübergabe vom DBMS an das Übersetzungsprogramm zu nennen — die Problematik dabei ist in Abschnitt 5.3.7 geschildert. Weiterhin ist die Bereitstellung einer grafischen Benutzeroberfläche (GUI) wünschenswert, die neben einer übersichtlicheren Darstellung in einem weiterem Schritt auch Unterstützung bei der Formulierung von SQLTE-Befehlen bieten könnte. Die Grundlagen dafür sind in Abschnitt 5.3.2 erläutert. Im Rahmen einer verfeinerten Benutzeroberfläche ist auch die Umsetzung einer alternativen Präsentation temporaler Anfrageergebnisse denkbar, wie dieses in Abschnitt 3.2.5 beschrieben wurde. Abschließend ist natürlich für die Vervollständigung des Übersetzungsprogramms auch eine Realisierung der vorab genannten theoretischen Spracherweiterungen als wünschenswert zu erwähnen. Anhang A Syntax der temporalen Erweiterung statement := query := query timeflag sfw timeflag ; dml ) ddl ) query UNION MINUS INTERSECT timeflag := [ PERIOD NONSEQUENCED , timestamp timestamp ) VALIDTIME timeflag sfw := ) * SELECT sfw FROM ) alias-id table-id scalarexp ... , , WHERE ... condexp , dml := ) INSERT INTO table-id VALUES WHERE DELETE FROM , ) column-id ) condexp table-id WHERE UPDATE table-id constant SET column-id = , 89 scalarexp condexp ) 90 ANHANG A. SYNTAX DER TEMPORALEN ERWEITERUNG ddl := table-id CREATE TABLE ( table-id column-id datatype ( DROP TABLE AS VALIDTIME , datatype := timestamp := NUMBER DATE ( VARCHAR integerval scalarexp := . NOW BEGINNING FOREVER dateval ‘ ‘ ( column-id condop := alias-id = > < <> constant ABS BEGIN END ( alias-id timeflag ( VALIDTIME >= <= OVERLAPS CONTAINS MEETS PRECEDES ( scalarexp ( sfw [ PERIOD , scalarexp ( scalarexp constant := + * / condexp := ( condexp ‘ timestamp integerval floatval stringval ( timeflag EXISTS ( ( sfw NOT scalarexp condop scalarexp ALL ANY SOME IS timeflag ( sfw ( NULL NOT NOT BETWEEN IN OR AND AND scalarexp ( sfw timeflag ( scalarexp ‘ Anhang B Dokumentation der Programmdateien Die in diesem Kapitel beschriebenen Dateien beinhalten die Implementation der temporalen Anfragesprache SQLTE. Die Darstellung erfolgt nach Modulen sortiert, mit Hilfe der Modulund Funktionsheader aus den Programmdateien. Die Dateien mit den Extensionen .h und .pc wurden mit Hilfe des Oracle Präprozessors Pro*C/C++ Release 8.1.51 und des GNU C Compilers GCC Version 2 übersetzt. Vor der ersten Verwendung des so erhaltenen Programms schafft die Ausführung der .sql-Dateien unter SQL*Plus Release 8.1.5 im gewünschten Datenbankschema die datenbankseitig benötigten Voraussetzungen. Es bedarf dafür entsprechender Rechte des Datenbankbenutzers, deren Existenz z. B. durch die Vergabe der vordefinierte Rolle connect sichergestellt werden. B.1 Datenbankstrukturen B.1.1 Hauptskript — sqlte.sql /***************************************************************************** * * * sqlte.sql - SQL-Skript zur Anlage der Datenbankstrukturen fuer SQLTE * * Definiert temporale Tabellen und Sequenz. Deklariert * * Package und Datentypen. Verwendet die Skriptdateien * * interval.sql, stamp.sql und package.sql um Package und * * Datentypen zu definieren. * * * * Dieses Skript muss vor der ersten Verwendung des Uebersetzers SQLTE * * ausgefuehrt werden, um die benoetigten Datenbankstrukturen zu schaffen. * *****************************************************************************/ 1 Tatsächlich erfolgte die Programmentwicklung sowohl mit Pro*C/C++ Release 8.1.5 als auch mit dem Präprozessor der Version 8.0.5. Das erstgenannte Produkt hat den leidigen Nachteil, daß z. B. ein fehlendes Semikolon am Ende einer Zeile der C-Quelldatei durch einen Programmabbruch mit der Nachricht segmentation fault (core dumped) quittiert wird — ohne Angabe der Fehlerposition. Solche Fehlerpositionen lassen sich dann mit Hilfe der Version 8.0.5 finden, diese kann jedoch die neuen Funktionen des ANSI dynamic SQL (s. a. Abschnitt 5.3.7) nicht übersetzen. 91 92 B.1.2 ANHANG B. DOKUMENTATION DER PROGRAMMDATEIEN Datentyp Intervall — interval.sql /***************************************************************************** * * * interval.sql - Definiert Body des Datentyps t_interval_type. * * * * Methoden: * * ti_intersection(ti in t_interval_type) return t_interval_type * *****************************************************************************/ /****************************************************************************** * * ti_intersection(ti in t_interval_type) return t_interval_type * Liefert den Schnitt zweier temporaler Intervalle oder NULL, * falls sich diese nicht ueberlappen. * * Eingabeparameter: * t_interval_type mit dem aufrufenden Objekt zu schneidendes Intervall * * Rueckgabewert: * t_interval_type Schnittintervall; NULL, falls kein Ueberlapp * */ B.1.3 Datentyp Zeitstempel — stamp.sql /***************************************************************************** * * * stamp.sql - Definiert Body des Datentyps t_stamp_type. * * * * Methoden: * * tp_intersection(tp in date) return date * * ti_intersection(ti in t_interval_type) return t_stamp_type * * te_intersection(te in t_stamp_type) return t_stamp_type * * ti_difference(ti in t_interval_type) return t_stamp_type * *****************************************************************************/ /****************************************************************************** * * tp_intersection(tp in date) return date * Liefert den Schnitt eines Zeitpunktes mit einem temporalem Element * oder NULL, falls der Zeitpunkt nicht ueberlappt wird. * * Eingabeparameter: * date mit dem aufrufenden Objekt zu schneidender Zeitpunkt * * Rueckgabewert: * date ueberlappter Zeitpunkt; NULL, falls kein Ueberlapp * */ B.1. DATENBANKSTRUKTUREN /****************************************************************************** * * ti_intersection(ti in t_interval_type) return t_stamp_type * Liefert den Schnitt eines Intervalls mit einem temporalem Element * oder NULL, falls sich diese nicht ueberlappen. * * Eingabeparameter: * t_interval_type mit dem aufrufenden Objekt zu schneidendes Intervall * * Rueckgabewert: * t_stamp_type Schnittintervall; NULL, falls kein Ueberlapp * */ /****************************************************************************** * * te_intersection(te in t_stamp_type) return t_stamp_type * Liefert den Schnitt zweier temporaler Elemente * oder NULL, falls sich diese nicht ueberlappen. * * Eingabeparameter: * t_stamp_type mit dem aufrufenden Objekt zu schneidendes temp. El. * * Rueckgabewert: * t_stamp_type Schnittintervall; NULL, falls kein Ueberlapp * */ /****************************************************************************** * * ti_difference(ti in t_interval_type) return t_stamp_type * Bildet die Differenz zwischen aufrufendem Objekt und dem angegebenen * Intervall. Liefert NULL, falls das Objekt komplett ueberlappt wird. * * Eingabeparameter: * t_interval_type das auszublendende Intervall * * Rueckgabewert: * t_stamp_type Differenz zwischen aufrufendem Objekt und Intervall * oder NULL, falls das Objekt komplett ueberlappt wird * */ B.1.4 Paketfunktionen — package.sql /***************************************************************************** * * * package.sql - Definiert Body des sqlte-Package. * * * * Methoden: * * te_intersection(te1 in t_stamp_type, ...) return t_stamp_type * * ts2string(ts in t_stamp_type) return varchar * * ti_subtraction(ti1 in t_interval_type, ti2 in t_interval_type) * * return t_stamp_type * *****************************************************************************/ 93 94 ANHANG B. DOKUMENTATION DER PROGRAMMDATEIEN /****************************************************************************** * * te_intersection(te1 in t_stamp_type, ...) return t_stamp_type * Liefert den Schnitt bis zu fuenf temporaler Elemente oder NULL, * falls sich diese nicht ueberlappen. * * Eingabeparameter: * t_stamp_type erstes zu schneidendes temporale Element * t_stamp_type zweites zu schneidendes temporale Element * (...) * * Rueckgabewert: * t_stamp_type Schnitt der Zeitstempel; NULL, falls kein Ueberlapp * */ /****************************************************************************** * * ts2string(ts in t_stamp_type) return varchar * Wandelt ein temporales Element durch aneinanderreihen der Intervalle * in eine Zeichenkette um; als Provisorium zur objektfreien Uebergabe * an das Pro*C-Uebersetzungsprogramm. * * Eingabeparameter: * t_stamp_type zu konvertierendes temporales Element * * Rueckgabewert: * varchar Zeichenkette der Art ’von;bis\von;bis\...’ * */ /****************************************************************************** * * ti_subtraction(ti1 in t_interval_type, ti2 in t_interval_type) * return t_stamp_type * Liefert die Differenz zweier Intervalle als temporales Element, * oder NULL, falls das erste Intervall komplett ueberlappt wird. * * Eingabeparameter: * t_interval_type Minuent * t_interval_type Subtrahent * * Rueckgabewert: * t_stamp_type Differenz * */ B.1.5 Deinstallationsskript — dropsqlte.sql Das Deinstallationsskript dropsqlte.sql sorgt für die Löschung aller durch sqlte.sql angelegten Datenbankobjekte. Voraussetzung für eine fehlerfreie Ausführung ist jedoch, daß keine Typen mehr aus sqlte.sql verwendet werden, d. h. es dürfen z. B. keine temporalen Tabellen mehr existieren. B.2. ÜBERSETZUNGSPROGRAMM B.1.6 95 Test des komplexen Speichermodells — kommode.zip Diese Archivdatei umfaßt die Testdateien zum zweifach geschachtelten Speichermodell aus Abschnitt 4.2.2. Obwohl dieses Speichermodell keine Anwendung findet, sollen dennoch die zugehörigen Funktionen und Datenstrukturen abgelegt werden, da diese für Weiterentwicklungen u. U. hilfreich sein können. Enthalten sind die Funktionen und Typdefinitionen aus Abschnitt 4.2.2 sowie einige Skripte zur Generierung von Tabellen und Testdaten. B.2 B.2.1 Übersetzungsprogramm Globale Strukturen — sqlte.h /***************************************************************************** * * * sqlte.h - globale Vereinbarungen fuer SQLTE; verwendet in allen Modulen * * * * global: * * Funktionen * * Konstanten * * Datentypen * * * * tdKnoten* pWurzel; Wurzelknoten des Parserbaums * * tdSymbol* pSymbol; zuletzt gescanntes Symbol * * tdKommando* pSQL92; Liste mit uebersetzten Kommandos * ***************************************************************************** B.2.2 Hauptprogramm — sqlte.pc /***************************************************************************** * * * sqlte.pc - Hauptprogramm der temporalen SQL-Erweiterung SQLTE * * * * global: * * int main(int argc, char *argv[]) * * * * tdKnoten* pWurzel = NULL; Wurzelknoten des Parserbaums * * tdSymbol* pSymbol = NULL; zuletzt gescanntes Symbol * * tdKommando* pSQL92 = NULL; Liste mit uebersetzten Kommandos * *****************************************************************************/ /****************************************************************************** * * int main(int argc, char *argv[]) * Hauptprogramm des SQLTE-Uebersetzers: uebernimmt Anmeldung, ruft * Parser, CodeGen und Execute auf. * * Eingabeparameter: * int argc Anzahl der Kommandozeilenparameter * char *argv[] Zeiger auf Kommandozeilenparameter * -q unterdrueckt Anzeige der SQLTE-Uebersetzung * * Rueckgabewert: * int Fehlerrueckgabe: 0, falls fehlerfrei */ 96 B.2.3 ANHANG B. DOKUMENTATION DER PROGRAMMDATEIEN Eingabeverarbeitung — eingabe.pc /***************************************************************************** * * * eingabe.pc - Funktionen zur Eingabeverarbeitung * * * * global: * * void InitEingabe(FILE *InitEingabe) * * void KommandoReInit(void) * * char KommandoZeichen(void) * * BOOL KommandoLeer(void) * * char AnmeldungZeichen(void) * * * * lokal: * * static FILE *Eingabe; * *****************************************************************************/ /****************************************************************************** * * void InitEingabe(FILE *InitEingabe) * Setzt die lokale(n) Variable(n) (derzeit nur eine) zur Eingabe. * * Eingabeparameter: * FILE * Zeiger auf Dateistruktur * */ /****************************************************************************** * * void KommandoReInit(void) * Setzt die Kommandoeingabe zurueck. * */ /****************************************************************************** * * char KommandoZeichen(void) * Liefert das naechste Zeichen der Kommandoeingabe. * * Rueckgabewert: * char eingelesenes Zeichen, falls Eingabe nichtleer * */ /****************************************************************************** * * BOOL KommandoLeer(void) * Prueft auf leere Kommandoeingabe. * * Rueckgabewert: * BOOL TRUE, falls keine weiteren Kommandozeichen vorhanden * */ B.2. ÜBERSETZUNGSPROGRAMM /****************************************************************************** * * char AnmeldungZeichen(void) * Liefert das naechste Zeichen der Anmeldung an die Datenbank. * * Rueckgabewert: * char eingelesenes Zeichen, falls Eingabe nichtleer * */ B.2.4 Ausgabeverarbeitung — ausgabe.pc /***************************************************************************** * * * ausgabe.pc - Funktionen zur Ausgabeverarbeitung * * * * global: * * void InitAusgabe(FILE *InitAusgabe) * * void ZeigeInfo(char *szInfo) * * void ZeigeErgebnis(char *szErgebnis) * * void ZeigeFehler(char *szFehler) * * void ZeigePrompt(void) * * * * lokal: * * static FILE *Ausgabe; * *****************************************************************************/ /****************************************************************************** * * void InitAusgabe(FILE *InitAusgabe) * Setzt die lokale(n) Variable(n) (derzeit nur eine) zur Ausgabe. * * Eingabeparameter: * FILE * Zeiger auf Dateistruktur * */ /****************************************************************************** * * void ZeigeInfo(char *szInfo) * Zeigt Zeichenkette zur Benutzerinformation. * * Eingabeparameter: * char * Zeiger auf ’\0’-Zeichenkette * */ /****************************************************************************** * * void ZeigeErgebnis(char *szErgbnis) * Zeigt Zeichenkette als Ergebnis einer Datenbankanfrage. * * Eingabeparameter: * char * Zeiger auf ’\0’-Zeichenkette * */ 97 98 ANHANG B. DOKUMENTATION DER PROGRAMMDATEIEN /****************************************************************************** * * void ZeigeFehler(char *szFehler) * Zeigt Zeichenkette als Fehlermeldung. * * Eingabeparameter: * char * Zeiger auf ’\0’-Zeichenkette * */ /****************************************************************************** * * void ZeigePrompt(void) * Zeigt Eingabeprompt. * */ B.2.5 Lexikalische Analyse — scanner.pc /***************************************************************************** * * * scanner.pc - Lexikalische Analyse von SQLTE-Kommandos * * * * global: * * BOOL ScannerInit(void) * * WORD Scanner(tdKnoten* pKnoten) * * BOOL Symbol2Text(char *szText, WORD wKonst) * * WORD Text2Symbol(char *szText) * * * * lokal: * * static void leseLeer(void); * * static WORD IdentifierTest(char *); * * static void ScannerFehler(char *); * * * * static char EingabeZeichen; Zuletzt gelesenes Eingabezeichen * * static WORD wZeile; Aktuelle Position in der * * static WORD wSpalte; Benuztereingabe * * static tdSymbol sSymbol; Zuletzt gelesenes Symbol * * static struct { * * char szText[STRINGLAENGE]; Klartext eines Symbols * * WORD wKonstante; Konstante eines Symbols * * } sTerminal[]; Uebersetzungstabelle der Symbolkonstanten * *****************************************************************************/ /****************************************************************************** * * BOOL ScannerInit(void) * Inititalisiert den Scanner, belegt lokale Variablen und * liest das erste Zeichen aus der Eingabe. * * Rueckgabewert: * BOOL TRUE, falls Initialisierung erfolgreich * */ B.2. ÜBERSETZUNGSPROGRAMM /****************************************************************************** * * WORD Scanner(tdKnoten* pKnoten) * Liefert das naechste Symbol aus der Benutzereingabe und legt das * zuvor gelesene Symbol (sofern vorhanden) im Parserbaum ab. * * Eingabeparameter: * tdKnoten* Zeiger auf Knoten im Parserbaum zur Aufnahme des * beim letzten Aufruf gelesenen Symbols * * Rueckgabewert: * WORD Symbolkonstante des eingelesenen Symbols * */ /****************************************************************************** * * BOOL Symbol2Text(char *szText, WORD wKonst) * Liefert zu einer Symbolkonstante den Klartext. * * Eingabeparameter: * char * Zeiger auf Zeichenkette zur Ablage des Ergebnis * WORD zu suchende Symbolskonstante * * Rueckgabewert: * BOOL TRUE, falls Suche erfolgreich * */ /****************************************************************************** * * WORD Text2Symbol(char *szText) * Liefert zum Klartext eines Symbols die zugehoerige Symbolkonstante. * * Eingabeparameter: * char * Zeiger auf ’\0’-Zeichenkette des Symboltextes * * Rueckgabewert: * WORD Symbolkonstante, falls Suche erfolgreich, sonst -1 * */ /****************************************************************************** * * void leseLeer(void) * Liest Leereingaben (blank, tab, enter) aus der Benutzereingabe * und zaehlt dabei Zeilen und Spalten. * */ 99 100 ANHANG B. DOKUMENTATION DER PROGRAMMDATEIEN /****************************************************************************** * * WORD IdentifierTest(char *szEingabe) * Entscheidet ueber den Inhalt einer Benutzereingabe, die keinem * terminalen Symbol zuzuweisen ist. * * Eingabeparameter: * char * Zeiger auf zu pruefende Benutzereingabe * * Rueckgabewert: * WORD tkIDENTIFIER, tkINTEGERVAL, tkFLOATVAL, tkSTRINGVAL * */ /****************************************************************************** * * void ScannerFehler(char *szFehler) * Gibt Fehlermeldung aus und bricht das Programm ab, * da hier interne Ursache (zuviele Symbole, zu lange Strings). * * Eingabeparameter: * char * Zeiger auf ’\0’-Zeichenkette mit Fehlermeldung * */ B.2.6 Syntaktische Analyse — parser.pc /***************************************************************************** * * * parser.pc - Syntaktische Analyse von SQLTE-Kommandos * * * * global: * * WORD Parser(void) * * * * lokal: * * static void pStatement(void); * * static void pTimeflag(tdKnoten*); * * static void pQuery(tdKnoten*); * * static void pSFW(tdKnoten*); * * static void pDML(tdKnoten*); * * static void pDDL(tdKnoten*); * * static void pDatatype(tdKnoten*); * * static void pCondExp(tdKnoten*); * * static void pCondOp(tdKnoten*); * * static void pScalarExp(tdKnoten*); * * static void pTimestamp(tdKnoten*); * * static void pConstant(tdKnoten*); * * static void ParserFehler(WORD); * * static tdKnoten* neuerKnoten(void); * * static void loescheKnoten(tdKnoten*); * * static tdKnoten* verzweigeKnoten(tdKnoten*); * * * * static WORD wToken; Zuletzt gescanntes Token * * static BOOL bFehler; TRUE, falls bereits Fehler beim Parsen * * static char* szFehlerMeldung[] Fehlermeldungen fuer ParserFehler * *****************************************************************************/ B.2. ÜBERSETZUNGSPROGRAMM /****************************************************************************** * * WORD Parser(void) * Syntaktische Analyse der Benutzereingabe und Aufbau des * Parserbaumes ausgehend von der globalen Variable pWurzel. * * Rueckgabewert: * WORD 0 = leere Eingabe * 1 = Eingabe syntaktisch fehlerhaft * 2 = fehlerfreie Eingabe * */ /****************************************************************************** * * void pStatement(void) * Beginnt rekursiven Abstieg des Parsers, initialisiert den * Parserbaum durch Verwendung der globalen Variable pWurzel. * */ /****************************************************************************** * * void pTimeflag(tdKnoten* p) * Setzt rekursiven Abstieg des Parsers fort, vgl. Syntaxgraphen. * Vergibt ggf. typNONSEQ, typSEQ und belegt den Zeiger pTimeflag. * * Eingabeparameter: * tdKnoten* Zeiger auf aktuellen Knoten im Parserbaum * */ /****************************************************************************** * * void pQuery(tdKnoten* p) * void pDatatype(tdKnoten* p) * void pCondExp(tdKnoten* p) * void pCondOp(tdKnoten* p) * void pScalarExp(tdKnoten* p) * void pTimestamp(tdKnoten* p) * void pConstant(tdKnoten* p) * Setzt rekursiven Abstieg des Parsers fort, vgl. Syntaxgraphen. * * Eingabeparameter: * tdKnoten* Zeiger auf aktuellen Knoten im Parserbaum * */ /****************************************************************************** * * void pSFW(tdKnoten* p) * Setzt rekursiven Abstieg des Parsers fort, vgl. Syntaxgraphen. * Vergibt ggf. typINLINEVIEW, typTABLEID und typJOIN. * * Eingabeparameter: * tdKnoten* Zeiger auf aktuellen Knoten im Parserbaum * */ 101 102 ANHANG B. DOKUMENTATION DER PROGRAMMDATEIEN /****************************************************************************** * * void pDML(tdKnoten* p) * Setzt rekursiven Abstieg des Parsers fort, vgl. Syntaxgraphen. * Vergibt ggf. typTABLEID. * * Eingabeparameter: * tdKnoten* Zeiger auf aktuellen Knoten im Parserbaum * */ /****************************************************************************** * * void pDDL(tdKnoten* p) * Setzt rekursiven Abstieg des Parsers fort, vgl. Syntaxgraphen. * Vergibt ggf. typTABLEID, typTEMP und typNONTEMP. * * Eingabeparameter: * tdKnoten* Zeiger auf aktuellen Knoten im Parserbaum * */ /****************************************************************************** * * void ParserFehler(WORD wFehlerNr) * Stellt syntaktische Fehler dar, durch Angabe von Text, Zeile, Spalte * und eines ’*’ an der fehlerhaften Position der Benutzereingabe. * * Eingabeparameter: * WORD Eine Symbolkonstante (z.B. tkSELECT) oder die Nummer * einer Fehlermeldung aus szFehlerMeldung[]. * */ /****************************************************************************** * * tdKnoten* neuerKnoten(void) * Schafft einen neuen Knoten und verzweigt den angegebenen Knoten des * Parserbaumes zu diesem neuen Knoten. * * Rueckgabewert: * tdKnoten* Zeiger auf den neu geschaffenen Knoten * */ /****************************************************************************** * * void loescheKnoten(tdKnoten* p) * Loescht einen Knoten des Parserbaumes, d.h. gibt den Speicher frei, * der durch den angegebenen Knoten und dessen Nachfolger belegt wird. * * Eingabeparameter: * tdKnoten* Zeiger auf den zu loeschenden Knoten * */ B.2. ÜBERSETZUNGSPROGRAMM /****************************************************************************** * * tdKnoten* verzweigeKnoten(tdKnoten* pAlt) * Schafft einen neuen Knoten und verzweigt den angegebenen Knoten des * Parserbaumes zu diesem neuen Knoten. * * Eingabeparameter: * tdKnoten* Zeiger auf aktuellen Knoten im Parserbaum, hier wird * an der naechsten freien Position die Verzweigung * eingetragen. * * Rueckgabewert: * tdKnoten* Zeiger auf den neu geschaffenen Knoten * */ B.2.7 Semantische Analyse und Übersetzung — codegen.pc /***************************************************************************** * * * codegen.pc - Funktionen zur Übersetzung von SQLTE-Kommandos. * * * * global: * * BOOL CodeGen(void) * * * * lokal: * * static BOOL Klartext(tdSymbol**, char *, tdSymbol*, WORD, WORD); * * static BOOL tStatement(void); * * static BOOL tQuery(tdKnoten*, char*); * * static BOOL tDDL(tdKnoten*, char*); * * static BOOL tDML(tdKnoten*, char*); * * static BOOL tSFW(tdKnoten*, char*); * * static BOOL tTimestamp(tdKnoten*, char*); * * static BOOL tTimeflag(tdKnoten*, char*); * * static BOOL tCondExp(tdKnoten*, char*); * * static BOOL ErgaenzeKnoten(tdKnoten*); * * static void loescheKommando(tdKommando* p); * * static void neuesKommando(char*, BOOL); * * * * static char szFehlerText[STRINGLAENGE]; temporaer fuer Fehlermeldungen * * static char szTemp[KOMMANDOLAENGE]; temporaer fuer Kommmandos * * static char szSysdate[STRINGLAENGE]; Systemzeit Transaktionsbeginn * * static WORD nTempTab; Zaehler temporaerer Tabellen * * je Transaktion * * static WORD nRekursion; Zaehler fuer Rekursionstiefe * * beim Uebersetzungsvorgang * * static char szKommando[KOMMANDOLAENGE]; Hauptkommando der Uebersetzung, * * wird an letzter Position in * * Liste pSQL92 eingefuegt * *****************************************************************************/ 103 104 ANHANG B. DOKUMENTATION DER PROGRAMMDATEIEN /****************************************************************************** * * BOOL CodeGen(void) * Liefert Uebersetzung der SQLTE-Anfrage aus dem Parserbaum (pWurzel) * in der verketteten Liste pSQL92 zurueck. Uebersetzung besteht i.A. * aus mehreren SQL92 Anweisungen. * * Rueckgabewert: * BOOL Ergebnis der Uebersetzung; TRUE, falls keine Fehler * */ /****************************************************************************** * * BOOL Klartext(tdSymbol** ppWeiter, char *szKommando, tdSymbol* pSymbol * , WORD wAnzahl, WORD wSymbol) * Steuert den rekursiven Uebersetzungsvorgang. Dazu werden Symbole in * zusammenhaengenden String umgewandelt, jeweils mit nachfolgenden * Leerzeichen. Enthalten die Symbole einen Verweis auf einen nach* folgenden Knoten, so wird dieser rekursiv uebersetzt und zwar, falls * vorhanden, mit der Knotenart zugehoerigen Uebersetzungsfunktion oder * sonst wieder durch kopieren der Klartexte. * Die angegebene Anzahl oder Suche entscheidet ueber Abbruch der * Kopierung. Nachfolgerknoten ohne Uebersetzungsfunktion werden * komplett kopiert, ohne Suche. * * Eingabeparameter: * tdSymbol** Nimmt die Position des naechsten Symbols im Knoten * auf, d.h. zeigt auf das erste nicht uebersetzte * Zeichen. * char * Zeichenkette zur Aufnahme der Uebersetztung, es * wird hier stets angehaengt * tdSymbol* erstes zu kopierendes Symbol * WORD Anzahl der zu kopierende Symbole oder 0, falls * nur Abbruch nach Suchkriterium erwuenscht * WORD Zu suchendes Symbol, dieses wird ggf. mit kopiert * und danach die Kopierung beendet. 0, falls keine * Suchfunktion erwuenscht. * * Rueckgabewert: * BOOL Ergebnis der Ausfuehrung; TRUE, falls keine Fehler * */ /****************************************************************************** * * BOOL tStatement(void) * Startet die rekursive Uebersetzung des SQLTE-Kommandos im Parser* baum. Als Datenquelle dienen dabei die Knoten unterhalb von pWurzel, * als Ziel hier die lokale Variable szKommando sowie die Liste pSQL92, * sofern mehr als ein Kommando zur Uebersetzung benoetigt wird. * Wird einmalig von codegen() aufgerufen. * */ B.2. ÜBERSETZUNGSPROGRAMM /****************************************************************************** * * BOOL tQuery(tdKnoten* pK, char*) * BOOL tDDL(tdKnoten* pK, char*) * BOOL tDML(tdKnoten* pK, char*) * BOOL tSFW(tdKnoten* pK, char*) * BOOL tTimestamp(tdKnoten* pK, char*) * BOOL tTimeflag(tdKnoten* pK, char*) * BOOL tCondExp(tdKnoten* pK, char*) * Uebernimmt die Uebersetzung eines Knotens der zugehoerigen Art. * Ergebnisse der Uebersetzung werden in szKommando bzw. in der Liste * pSQL92 abgelegt, sofern mehr als ein Kommando zur Uebersetzung * benoetigt wird. * * Eingabeparameter: * tdKnoten* Zeiger auf den zu uebersetzenden Knoten * char* hier wird die Uebersetzung angehaengt * * Rueckgabewert: * BOOL TRUE, falls Uebersetzung erfolgreich * */ /****************************************************************************** * * BOOL ErgaenzeKnoten(tdKnoten *p) * Ergaenzt Parserknoten um zusaetzliche semantische Informationen, * etwa ob Tabellen temporal oder nicht. Ueberprueft die Typvertraeg* lichkeit von Tabellen/Anfragen, weist ggf. auf noch nicht implemen* tierte Funktionen der Sprache (z.B. NONSEQ) hin. Wird rekursiv * verwendet und von codegen mit dem Knoten pWurzel aufgerufen. * * Eingabeparameter: * tdKnoten* erster zu ergaenzender und zu pruefender Knoten * * Rueckgabewert: * BOOL Ergebnis der Ausfuehrung; TRUE, falls keine Fehler * */ /****************************************************************************** * * void loescheKommando(tdKommando* p) * Loescht rekursiv die verkettete Listenstruktur zur Aufnahem der * uebersetzten Kommandos, d.h. der dynamisch belegte Speicher wird * freigegeben. * * Eingabeparameter: * tdKommando* Erster zu loeschender Listeneintrag * */ 105 106 ANHANG B. DOKUMENTATION DER PROGRAMMDATEIEN /****************************************************************************** * * void neuesKommando(char *szText, BOOL) * Erzeugt einen neuen Eintrag am Ende der Listenstruktur zur Aufnahme * der uebersetzten Kommandos und legt dort die angegebene * Zeichenkette ab. Fuehrt das neue Kommando bei Bedarf sofort aus. * * Eingabeparameter: * char * Zeiger auf Zeichenkette mit Kommando * BOOL TRUE, falls Kommando sofort ausgefuehrt werden soll * */ B.2.8 Datenbankschnittstelle — oracle.pc /***************************************************************************** * * * oracle.pc - Funktionen zur Kommunikation mit Oracle fuer SQLTE * * * * global: * * void sqlConnect(char *, char *); * * void sqlDisconnect(void); * * BOOL sqlExecute(char *); * * BOOL sqlCommit(void); * * BOOL sqlTEexists(void); * * BOOL sqlTableTemporal(char *); * * BOOL sqlTableExists(char *); * * BOOL sqlStrcatColumns(char *, char *); * * void sqlDropTempTab(void); * * BOOL sqlGetSysdate(char *); * * * * lokal: * * static void sqlError(void); * * static void ZeigeAnfrageErgebnis(void); * * static void ZeigeZeitstempel(char*, WORD); * * static void VerschmelzeZeitstempel(char*, char*, char*); * * static void Test(void); * * static void Test2(void); * * * * static char* szBuf[MAXSPAL][MAXZEIL] Puffer fuer Anfrageergebnisse * * static short Indi[MAXSPAL][MAXZEIL] Indikator fuer NULL bei Anf.Erg. * * static VARCHAR username[USER_LEN]; Benutzername zur DB-Anmeldung * * static VARCHAR password[PWD_LEN]; Passwort zur DB-Anmeldung * * static char* szOraDynStatement; dynamische SQL-Kommandos * *****************************************************************************/ /****************************************************************************** * * void sqlConnect(char *szUser, char *szPassword) * Stellt Verbindung zur Datenbank her. * * Eingabeparameter: * char * Zeiger auf Zeichenkette mit Benutzername@DB-Alias * char * Zeiger auf Zeichenkette mit Passwort * */ B.2. ÜBERSETZUNGSPROGRAMM /****************************************************************************** * * void sqlDisconnect(void) * Unterbricht Verbindung zur Datenbank, fuehrt COMMIT aus. * */ /****************************************************************************** * * BOOL sqlExecute(char *) * Fuehrt Oracle-Statement dynamisch aus. * * Eingabeparameter: * char * Zeiger auf ’\0’-Zeichenkette mit Kommando * * Rueckgabewert: * BOOL Ergebnis der Ausfuehrung; TRUE, falls keine Fehler * */ /****************************************************************************** * * BOOL sqlCommit(void) * Fuehrt COMMIT WORK statisch aus. * * Rueckgabewert: * BOOL Ergebnis der Ausfuehrung; TRUE, falls keine Fehler * */ /****************************************************************************** * * BOOL sqlTEexists(void) * Prueft auf Existenz der Sequenz rwo_nr_seq_ und stellt damit sicher, * dass SQLTE.SQL bereits ausgefuehrt wurde. * * Rueckgabewert: * BOOL TRUE, falls Sequenz vorhanden * */ /****************************************************************************** * * BOOL sqlTableTemporal(char *szTable) * Prueft, ob die angegebene Tabelle temporal erweitert wurde, d.h. ob * die nested table <table>_ntabl_ im data dictionary existiert. * * Eingabeparameter: * char * Zeiger auf Zeichenkette mit Tabellennamen * * Rueckgabewert: * BOOL TRUE, falls Tabelle temporal * */ 107 108 ANHANG B. DOKUMENTATION DER PROGRAMMDATEIEN /****************************************************************************** * * BOOL sqlTableExists(char *szTable) * Prueft, ob die angegebene Tabelle im data dictionary existiert. * * Eingabeparameter: * char * Zeiger auf Zeichenkette mit Tabellennamen * * Rueckgabewert: * BOOL TRUE, falls Tabelle vorhanden * */ /****************************************************************************** * * BOOL sqlStrcatColumns(char *, char *) * liefert die Spalten einer Tabelle in der definierte Reihenfolge, * ohne die evtl. vorhandenen internen Spalten RWO_NR_ und TS_ * * Eingabeparameter: * char* zur Aufnahme der Spaltennamen der Form "c1, c2, ..." * char* Zeichenkette mit Tabellennamen * * Rueckgabewert: * BOOL Ergebnis der Ausfuehrung; TRUE, falls keine Fehler * */ /****************************************************************************** * * void sqlDropTempTab(void) * Loescht die temporaeren Tabellen der letzten Transaktion. * */ /****************************************************************************** * * BOOL sqlGetSysdate(char *szTimestamp) * Liefert die aktuelle Systemzeit der Datenbank. * * Eingabeparameter: * char * Zeiger auf Zeichenkette zur Zeitaufnahme * * Rueckgabewert: * BOOL Ergebnis der Ausfuehrung; TRUE, falls keine Fehler * */ /****************************************************************************** * * void sqlError(void) * Zeigt Oracle-Fehlermeldung aus Oracle-eigener sqlca-Struktur an. * Funktion wird durch sqlConnect() als globale SQL-Fehlerprozedur * eingerichtet. * */ B.2. ÜBERSETZUNGSPROGRAMM /****************************************************************************** * * void ZeigeAnfrageErgebnis(void) * Liefert eine einfache formatierte Ausgabe der letzten ueber * ’output_descriptor’ ausgefuehrten dynamischen Anfrage. * Ist die Spalte RWO_NR_ beteiligt, wird von einer temporalen * Anfrage ausgegangen: Die Zeilen werden dann bei identischer * RWO_NR_ zusammen dargestellt und die Daten des Zeitstempels * entsprechend formatiert. * */ /****************************************************************************** * * void ZeigeZeitstempel(char *szZeitstempel, WORD nLeer) * Fuehrt die formatierte Ausgabe eines Zeitstempels aus, beachtet * insbesondere die mehrzeilige Ausgabe bei >1 Intervallen. * * Eingabeparameter: * char * Zeiger auf Zeichenkette mit temporalem Element * (durch die Trennzeichen ; und \ strukturiert) * WORD Position der Intervalle fuer die Bildschirmausgabe * */ /****************************************************************************** * * void VerschmelzeZeitstempel(char* szZiel, char* szQ1, char*szQ2) * Fuegt zwei temporale Elemente in Zeichenkettenform zusammen und * nimmt dabei, falls moeglich, Zusammenfassung der Intervalle vor. * * Eingabeparameter: * char * Zeiger auf neue Zeitstempel-Zeichenkette * char * Zeiger auf erste alte Zeitstempel-Zeichenkette * char * Zeiger auf zweite alte Zeitstempel-Zeichenkette * */ /****************************************************************************** * * void Test(void) * Diese Funktion probiert die Uebergabe von Objekten - leider erfolglos * mit der Meldung ’inconsistent datatypes’ bei fetch. Vielleicht ist * die Kombination dynamisches sql/pro*c/objects in Zukunft besser * dokumentiert, so dass sich diese Funktion nachruesten laesst. :-) * */ /****************************************************************************** * * void Test2(void) * Diese Funktion probiert die Uebergabe von Objekten bei statischem * embedded sql - erhoffte Erkenntnisse fuer den dynamischen Zugriff * in der Funktion Test() sind leider ausgeblieben: Der statische * Objekt-Zugriff funktioniert hier problemlos. * */ 109 Literaturverzeichnis [BBJ96] Bair J., Böhlen M. H., Jensen C. S., Snodgrass R. T. (1996): Notions of Upward Compatibility of Temporal Query Languages. Dept. of Mathematics and Computer Science, University of Aalborg [Böh94] Böhlen M. H. (1994): Managing Temporal Knowledge in Deductive Databases. PhD thesis, Institute for Information Systems, ETH Zürich [Böh95] Böhlen M. H. (1995): Temporal Database System Implementations. SIGMOD Record, Volume 24, Number 4 [Böh97] Böhlen M. H. (1997): Tiger Reference Manual. Dept. of Mathematics and Computer Science, University of Aalborg, http://www.cs.auc.dk [BJ96] Böhlen M. H., Jensen C. S. (1996): Seamless Integration of Time into SQL. Technical Report R-962049, Dept. of Mathematics and Computer Science, University of Aalborg, Denmark [BJS95] Böhlen M. H., Jensen C. S., Snodgrass R. T. (1995): Evaluating and Enhancing the Completeness of TSQL2. Technical Report TR 95-05, Computer Science Department, University of Arizona [BSS96] Böhlen M. H., Snodgrass R. T., Soo M. D. (1996): Coalescing in Temporal Databases. In Vijayaraman T.M., Buchmann A., Mohan C., Sarda N.L. (editors): Proceedings of the 22nd International Conference on Very Large Data Bases. Mumbai (Bombay), India: Morgan Kaufmann Publishers [EJS98] Etzion O., Jajodia S., Sripada S., Hrsg. (1998): Temporal Databases: Research and Practice. Lecture Notes in Computer Science 1399. Berlin: Springer-Verlag [GV85] Gadia S. K., Vaishnav J. H. (1985): A Query Language for a Homogeneous Temporal Database. In Proceedings of the International Conference on Management of Data, 1985, p. 51–56 [HP98] Hohenstein U., Pleßler V. (1998): Oracle 8: Effiziente Anwendungsentwicklung mit objektrelationalen Konzepten. Heidelberg: dpunkt-Verlag [JSS93] Jensen C. S., Soo M. D., Snodgrass R. T. (1993): Unifying Temporal Data Models via a Conceptual Model. Technical Report TR 93-31, Computer Science Department, University of Arizona [Kop96] Kopka H. (1996): LaTex: Einführung. 2. Auflage, Bonn: Addison-Wesley 110 LITERATURVERZEICHNIS 111 [KL97] Koch G., Loney K. (1997): Oracle 8: The Complete Reference. Berkeley, CA: Osborne / McGraw-Hill [KR83] Kernighan B. W., Ritchie D. M. (1983): Programmieren in C. Wien: Hanser [Myr97] Myrach T. (1997): TSQL2: Der Konsens über eine temporale Datenbanksprache. Informatik Spektrum 20(3): 143–150 [MS93] Melton J., Simon A. R. (1993): Understanding the new SQL: A Complete Guide. San Mateo, CA: Morgan Kaufmann Publishers [NA93] Navathe S., Ahmed R. (1993): Temporal Extensions to the Relational Model and SQL. In [TCG+93], p. 92–109 [Ora98a] Server Documentation (Rel. 8.0.5): Concepts. Part Number A58227-01 [Ora98b] Server Documentation (Rel. 8.0.5): SQL Reference. Part Number A58225-01 [Ora98c] Server Documentation (Rel. 8.0.5): Application Developer’s Guide. Part Number A58241-01 [Ora98d] Server Documentation (Rel. 8.0.5): PL/SQL User’s Guide and Reference. Part Number A58236-01 [Ora98e] Server Documentation (Rel. 8.0.5): Error Messages. Part Number A58312-01 [Ora99a] Server Documentation (Rel. 8.1.5): Concepts. Part Number A67781-01 [Ora99b] Server Documentation (Rel. 8.1.5): SQL Reference. Part Number A67779-01 [Ora99c] Server Documentation (Rel. 8.1.5): Application Developer’s Guide — Fundamentals. Part Number A68003-01 [Ora99d] Server Documentation (Rel. 8.1.5): PL/SQL User’s Guide and Reference. Part Number A67842-01 [Ora99e] Server Documentation (Rel. 8.1.5): Error Messages. Part Number A67785-01 [Ora99f] Server Documentation (Rel. 8.1.5): Getting to Know Oracle 8i. Part Number A6802001 [Ora99g] Server Documentation (Rel. 8.1.5): Pro*C/C++ Precompiler Programmer’s Guide. Part Number A68022-01 [Ora99h] Server Documentation (Rel. 8.1.5): Oracle Call Interface Programmer’s Guide. Part Number A67846-01 [Par91] Parchmann R. (1991): Skript zu den Vorlesungen Informatik I-IV 2. Auflage, Institut für Informatik, Universität Hannover [Pul95] Pulfer D. (1995): Optimierung von temporalen Queries. Master´s thesis, Institute for Information Systems, ETH Zürich [SAA94a] Snodgrass R. T., Ahn I., Ariav G., et al (94): A TSQL2 Tutorial. SIGMOD Record 23:3, p. 27 112 LITERATURVERZEICHNIS [SAA94b] Snodgrass R. T., Ahn I., Ariav G., et al (94): TSQL2 Language Specification. SIGMOD Record 23:1, p. 65 [SBJ96a] Snodgrass R. T., Böhlen M. H., Jensen C. S., Steiner A. (1996): Adding Valid Time to SQL/Temporal. ISO/IEC JTC1/SC21/WG3 DBL MCI-142 [SBJ96b] Snodgrass R. T., Böhlen M. H., Jensen C. S., Steiner A. (1996): Adding Transaction Time to SQL/Temporal. ISO/IEC JTC1/SC21/WG3 DBL MCI-143 [SBJ97] Snodgrass R. T., Böhlen M. H., Jensen C. S., Steiner A. (1997): Transitioning Temporal Support in TSQL2 to SQL3. In [EJS98], p. 151–194 [Sno87] Snodgrass R. T. (1987): The Temporal Query Language TQuel. ACM Transactions on Database Systems, 12(2): 247–298, June 1987 [Sno95] Snodgrass R. T. (editor) (1995): The Temporal Query Language TSQL2. Norwell, MA: Kluwer Academic Publishers [Sno00] Snodgrass R. T. (2000): Developing Time-Oriented Database Applications in SQL. San Francisco, CA: Morgan Kaufmann Publishers [SQL92] American National Standards Institute (1992): Information Systems — Database Language — SQL. ANSI X3.135-1992 [SQL99] International Organization for Standardization (1999): Temporal (SQL/Temporal) (ISO Working Draft). ISO/IEC JTC 1/SC 21/WG 3 [Ste98a] Steiner A. (1998): A Generalisation Approach to Temporal Data Models and their Implementations. Ph. D. Thesis, ETH Zürich [Ste98b] Steiner A. (1998): TimeDB 2.0 Documentation. http://www.timeconsult.com [TCG93] Tansel A. U., Clifford J., Gadia S., Jajodia S., Segev A., Snodgrass R. T. (1993): Temporal Databases: Theory, Design and Implementation. Redwood City, CA: Benjamin/Cummings [TJB97] Torp K., Jensen C. S., Böhlen M. H. (1997): Layered Implementation of Temporal DBMSs — Concepts and Techniques. TimeCenter Technical Report TR-2, http://www.cs.arizona.edu [Urm97] Urman S. (1997): Oracle 8: PL/SQL Programming. Berkeley, CA: Osborne/McGrawHill [Vos99] Vossen G. (1999): Datenmodelle, Datenbanksprachen und Datenbank-ManagementSysteme. 3. Auflage, München: Oldenbourg-Verlag [Wir96] Wirth N. (1996): Grundlagen und Techniken des Compilerbaus. Bonn: AddisonWesley [ZCF97] Zaniolo C., Ceri S., Faloutsos C., Snodgrass R. T., Subrahmanian V.S., Zicari R. (1997): Advanced Database Systems. San Francisco, CA: Morgan Kaufmann Publishers Abbildungsverzeichnis 2.1 Ausführung einer temporal aufwärtskompatiblen Anfrage (nach [BJ96]) . . . . . 14 2.2 Beispiel zur Ausführung einer temporal aufwärtskompatiblen Anfrage . . . . . . 15 2.3 Ausführung einer sequentiellen Anfrage (nach [BJ96]) . . . . . . . . . . . . . . . 16 2.4 Ausführung einer nicht-sequentiellen Anfrage (nach [BJ96]) . . . . . . . . . . . . 18 3.1 Nicht-temporale Relationenalgebra . . . . . . . . . . . . . . . . . . . . . . . . . . 25 3.2 Differenz-Operator der temporalen Relationenalgebra (nach [BJ96]) . . . . . . . . 26 3.3 Verschmelzungs-Operator der temporalen Relationenalgebra (nach [BJ96]) . . . . 27 3.4 Syntax der SQLTE-Befehle zur Anfrageformulierung . . . . . . . . . . . . . . . . 31 3.5 Syntax der SQLTE-Befehle zur Datenmanipulation . . . . . . . . . . . . . . . . . 35 3.6 Zeitstempel bei der sequentiellen Update-Operation . . . . . . . . . . . . . . . . 36 3.7 Syntax der SQLTE-Befehle zur Datendefinition . . . . . . . . . . . . . . . . . . . 38 4.1 Kollektionswertiges Attribut als nested table-Typ . . . . . . . . . . . . . . . . . . 42 4.2 Nested table-Typ als index organized table (IOT) . . . . . . . . . . . . . . . . . . 43 4.3 Zugriff auf Kollektionen unter PL/SQL (nach [Ora98d]) . . . . . . . . . . . . . . 45 4.4 Temporale Tabelle Ang im einfach geschachtelten Speichermodell . . . . . . . . . 49 4.5 Temporale Tabelle Ang im doppelt geschachtelten Speichermodell . . . . . . . . 54 4.6 Schichtenarchitektur temporaler Datenbanken (nach [TJB97]) . . . . . . . . . . . 61 5.1 Datenflußdiagramm der SQLTE-Implementation . . . . . . . . . . . . . . . . . . 64 5.2 Abhängigkeitsgraph der Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 5.3 Schematische Darstellung eines Parserbaums . . . . . . . . . . . . . . . . . . . . 73 113 Erklärung Hiermit erkläre ich, die vorliegende Diplomarbeit Realisierung einer temporalen Erweiterung ” von SQL auf einem objekt-relationalen Datenbankmanagementsystem“ selbständig verfaßt und keine anderen als die angegebenen Quellen und Hilfsmittel verwendet zu haben. Hannover, 22. Dezember 1999 114