Implementation von benutzerdefinierten Indexen für geometrische Strukturen in Oracle 8i unter Verwendung von z-Codes und eines R*-Baumes mit mehrfachem Clipping länglicher Objekte. Studienarbeit von Sebastian Schersich und Michael Standke Betreuer: Carsten Kleiner, Thomas Esser 18. Januar 2001 . Einleitung Mit objektrelationalen Datenbank-Managementsystemen wie Oracle 8i können neben Standarddatentypen auch objektwertige Datentypen verwaltet werden. Bei größeren Datenbeständen ist es sinnvoll, die Daten zu indexieren, um die Zugriffsgeschwindigkeit zu erhöhen. Datenbank-Managementsysteme bieten zu diesem Zweck in der Regel Indextypen für die gebräuchlichen Standarddatentypen. Für benutzerdefinierte (objektwertige) Datentypen ist es allerdings nötig, einen benutzerdefinierten Indextyp bereitzustellen. In der aktuellen Version von Oracle 8i wird die Erweiterung Oracle Spatial [Cor99] für räumliche Daten unter Verwendung der objektrelationalen Technik unterstützt. Spatial benutzt das Zwei-Ebenen-Anfrage-Modell (two-tier-query model), um Anfragen auf räumliche Daten zu bearbeiten (vgl. Abb.1). Eine räumliche Anfrage wird dabei in zwei Operationen (Primärfilter und Sekundärfilter) aufgeteilt. Aufgabe des Primärfilters ist es, aus der Gesamtmenge der geometrischen Daten in möglichst kurzer Zeit eine Teilmenge (Kandidatenmenge) herauszufiltern, die als Eingabe an den Sekundärfilter übergeben wird. Diese indexbasierte PrimärfilterOperation benutzt als Eingabe approximierte Geometrieinformationen, um den Rechenaufwand zu verringern. Das Ergebnis der Operation des Primärfilters ist eine Obermenge des exakten Resultats. Der Sekundärfilter führt anschließend exakte und daher meist teure Operationen auf der vom Primärfilter übergebenen Kandidatenmenge aus und liefert die exakte Ergebnismenge der Anfrage. P r i m Z M g e o e m n g D e e a t r i s d t e c e - C ä o r f i l t e d e S r e n b n C p l i p i n g - z R w k u n d ä r f i l t e r s r h e K . * - B a a m n u d i d e n g a e t e n - e g O e o x p m e a k t e e t r i s c r a t o r e h e n e E r g e x b a n k i s t e m e n g e m Abb. 1: 2-Ebenen-Anfrage-Modell Im Rahmen dieser Studienarbeit sollte zu einem der Oracle Spatial Index nachimplementiert werden. Dieser basiert auf z-Codes und ist leider nur als Black Box verfügbar, sodaß interne Strukturen nicht analysiert und modifiziert werden können. Zum anderen sollte eine Indexstruktur erstellt werden, die auf Basis eines R*-Baumes arbeitet, welcher schon in einer Studienarbeit vorlag. Um die exakte Ergebnismenge zu bestimmen war es noch notwendig geometrische Operatoren zu implementieren. Es wurde aber nur der Intersects-Operator implementiert, der prüft, ob sich zwei Objekte schneiden, da sonst der zeitliche Rahmen der Studienarbeit gesprengt worden wäre. 3 Inhaltsverzeichnis I Allgemeines 8 1 R*-Baum-Index 1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . 1.2 Aufbau des R*-Baumes . . . . . . . . . . . . . . . 1.3 Aufbau des R*-Baum-Indexes . . . . . . . . . . . . 1.4 Algorithmen . . . . . . . . . . . . . . . . . . . . . . 1.4.1 R*-Baum-Index . . . . . . . . . . . . . . . . 1.4.2 Berechnen des approxmierenden Rechtecks . 1.4.3 Beurteilung der Güte einer Approximation . 1.4.4 Clippen von Streckenzügen . . . . . . . . . 2 z-Codes 2.1 Motivation . . . . . . . . . . 2.2 Prinzip . . . . . . . . . . . . . 2.3 Algorithmen . . . . . . . . . . 2.3.1 Erzeugen von z-Codes . . . . . . . . . . . . . . . . . . . . 3 Exakte geometrische Operatoren 3.1 Motivation . . . . . . . . . . . . . . . 3.2 Aufbau . . . . . . . . . . . . . . . . . . 3.3 Algorithmen . . . . . . . . . . . . . . . 3.3.1 Planesweep-Algorithmus . . . . 3.3.2 Schnitt zweier Liniensegmente . 3.3.3 Schnittprüfung für Polygon mit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 8 8 9 11 11 11 12 12 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 15 15 18 18 . . . . . . . . . . . . . . . Loch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 19 19 20 20 21 21 . . . . . . . . II Oracle 8i 4 Extensible Indexing Interface 4.1 ODCIGetInterfaces . . . . . 4.2 ODCIIndexCreate . . . . . 4.3 ODCIIndexInsert . . . . . . 4.4 ODCIIndexAlter . . . . . . 4.5 ODCIIndexUpdate . . . . . 4.6 ODCIIndexDelete . . . . . . 4.7 ODCIIndexTruncate . . . . 4.8 ODCIIndexStart . . . . . . 4.9 ODCIIndexFetch . . . . . . 4.10 ODCIIndexClose . . . . . . 4.11 ODCIIndexDrop . . . . . . 23 . . . . . . . . . . . 23 23 23 24 24 24 24 25 25 25 26 26 5 Oracle Call Interface 5.1 HandleKonzept . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2 Statements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3 Speicherverwaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 26 27 27 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 Oracle Shared Library 27 III Implementation 28 7 R*-Baum 7.1 PL/SQL . . . . . . . . . . . . . . 7.1.1 RSTConf.sql . . . . . . . 7.1.2 RSTPath.sql . . . . . . . 7.1.3 RSTTreetable.sql . . . . . 7.1.4 RSTSupp.sql . . . . . . . 7.1.5 RSTRectangle.sql . . . . . 7.1.6 RSTOps.sql . . . . . . . . 7.1.7 RSTIndex.sql . . . . . . . 7.1.8 RSTRemoveIndextype.sql 7.1.9 RSTRemoveAll.sql . . . . 7.2 C-Modul RSTSupp . . . . . . . . 7.2.1 GetDBHandles . . . . . . 7.2.2 checkErr . . . . . . . . . . 7.3 C-Modul RSTClip . . . . . . . . 7.3.1 createRects . . . . . . . . 7.3.2 insertRects . . . . . . . . 7.3.3 getType1Values . . . . . . 7.3.4 getType2Values . . . . . . 7.3.5 getType3Values . . . . . . 7.3.6 clipLineString . . . . . . . 7.3.7 insertRect . . . . . . . . . 8 z-Codes 8.1 PL/SQL . . . . . . . . . . . . 8.1.1 zcode index.sql . . . . 8.1.2 zcodelib.sql . . . . . . 8.1.3 zcode ops.sql . . . . . 8.2 Tabellen . . . . . . . . . . . . 8.3 Modul index zcode . . . . . . 8.3.1 index create . . . . . . 8.3.2 index insert . . . . . . 8.3.3 index delete . . . . . . 8.3.4 index start . . . . . . 8.3.5 index fetch . . . . . . 8.3.6 index close . . . . . . 8.3.7 test data . . . . . . . 8.4 Modul index metadata . . . . 8.4.1 indexinfo type . . . . 8.4.2 index read metadata . 8.4.3 index write metadata 8.4.4 index delete metadata 8.5 Modul index scancontext . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 28 28 28 28 28 28 29 29 31 31 31 31 31 31 32 32 33 33 33 33 34 . . . . . . . . . . . . . . . . . . . 35 35 35 35 35 36 37 37 37 37 38 38 38 38 39 39 39 40 40 40 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6 Modul 8.6.1 8.6.2 8.6.3 8.7 Modul 8.7.1 8.7.2 8.8 Modul 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9 Modul 8.10 Modul 8.10.1 8.10.2 CreateScanContext . . . . . . . FillScanContext . . . . . . . . GetScanContext . . . . . . . . DisposeScanContext . . . . . . Datentyp ScanCtx . . . . . . . DBUtils . . . . . . . . . . . . . connectByCtx . . . . . . . . . . oraCheckErr . . . . . . . . . . . Datentyp DBConnectionHandle geom zcode . . . . . . . . . . . zcode calc . . . . . . . . . . . . zcode createquery . . . . . . . . geom op . . . . . . . . . . . . . GetVArray( ws) . . . . . . . . GetVArray info( ws) . . . . . . SetVArray( ws) . . . . . . . . . SetVArray info( ws) . . . . . . GetSDO Point( ws) . . . . . . GetRectangle( ws) . . . . . . . Datentyp point type( ws) . . . geom data . . . . . . . . . . . . geom err . . . . . . . . . . . . . checkerr . . . . . . . . . . . . . printerror ora . . . . . . . . . . 9 Exakte geometrische Operationen 9.1 PL/SQL . . . . . . . . . . . . . . 9.1.1 MyIntersects.sql . . . . . 9.2 C-Modul RSTOps . . . . . . . . 9.2.1 callOps . . . . . . . . . . 9.2.2 intersectsRetNum . . . . . 9.2.3 intersectsRetInt . . . . . . 9.2.4 intersects . . . . . . . . . 9.2.5 getIntoQList . . . . . . . 9.2.6 insertLineIntoQ . . . . . . 9.2.7 planeSweep . . . . . . . . 9.2.8 insertIntoLList . . . . . . 9.2.9 deleteFromLList . . . . . 9.2.10 checkIntersect . . . . . . . 9.2.11 getStatus . . . . . . . . . 9.2.12 freeExtProcMem . . . . . IV Benutzung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 40 41 41 41 41 41 41 42 42 42 42 42 43 43 43 43 43 44 44 44 44 44 44 . . . . . . . . . . . . . . . 45 45 45 45 45 46 46 46 46 47 47 47 47 48 48 48 49 6 10 R*-Baum mit Clipping 10.1 Installation . . . . . . . . . . . . . . . . . . 10.2 Anlegen eines Indexes . . . . . . . . . . . . 10.3 Anfragen stellen . . . . . . . . . . . . . . . 10.4 Deinstallation . . . . . . . . . . . . . . . . . 10.4.1 Löschen eines R*-Baum-Indextyps . 10.4.2 Deinstallation des R*-Baum-Indexes . . . . . . 49 49 49 50 50 50 50 11 z-Codes 11.1 Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.2 Anlegen eines z-Code-Index . . . . . . . . . . . . . . . . . . . . . . . 11.3 Anfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 51 51 52 12 Intersects-Operator 12.1 Anlegen des Intersects-Operators . . . . . . . . . . . . . . . . . . . . 12.2 Anfragen stellen mit dem Intersects-Operator . . . . . . . . . . . . . 53 53 53 V Tests 54 13 R*-Baum 13.1 Zeitbedarf für das Anlegen eines R*-Baum-Indexes . . . . . . . . . . 13.2 Zeitbedarf für das Selektieren von Daten mit Hilfe des IntersectsOperators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 54 14 z-Codes 14.1 Zeitbedarf fürs Anlegen eines z-Codes Indexes 14.2 Zeitbedarf fürs Ausführen einer Anfrage . . . 14.2.1 Primärfilter . . . . . . . . . . . . . . . 14.2.2 Intersects . . . . . . . . . . . . . . . . 57 57 57 57 58 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 VI Fazit und Anmerkungen 59 15 R*-Baum 59 16 z-Codes 59 7 Teil I Allgemeines 1 R*-Baum-Index 1.1 Motivation In einer Studienarbeit von Sascha Klopp [Klo99] wurde der in [BKSS90] beschriebene R*-Baum in PL/SQL implementiert. Es handelt sich um eine Weiterentwicklung des R-Baums, einer Indexstruktur für mehrdimensionale Daten [Gut84]. Dazu wurde in Oracle 8i ein neuer Indextyp mit Hilfe des Extensible Indexing Interface angelegt. Da der R*-Baum nur mit Rechtecken arbeitet, und als Indexstruktur auf Rechteckdaten implementiert wurde, die zu verarbeitenen Geometriedaten aber im Format MDSYS.SDO_GEOMETRY vorliegen, mußten Veränderungen am R*-Baum-Index vorgenommen werden. Um diese Modifikation möglichst gering zu halten, sollte ein vorgeschaltetes Modul die Geometriedaten durch Rechtecke approximieren und dem R*-Baum zu Verfügung stellen. Die Approximation von geometrischen Objekten durch Rechtecke kann aber aufgrund der Form und Lage dieser Objekte in der Ebene schlecht ausfallen. Insbesondere bei Linienzügen kann dies negative Auswirkungen auf die Arbeitsweise des Primärfilters haben, da dieser unter Umständen eine zu große Kandidatenmenge zurückliefert. Um die Approximation zu verbessern, stellt sich die Aufgabe, solche Objekte mit einem Clipping-Algorithmus in kleinere Strukturen zu zerlegen, bei denen die umgebenen Rechtecke eine bessere Approximation liefern. Abb. 2: Standard-Approximation und Approximation mit Clipping 1.2 Aufbau des R*-Baumes Im folgenden soll kurz der Aufbau des R*-Baum skizziert werden. Für eine ausführliche Beschreibung wird auf die Studienarbeit [Klo99] verwiesen. Der R*-Baum ähnelt in seinem Aufau stark dem B*-Baum, allerdings benötigt er aufwendigere Algorithmen zum Einfügen und Selektieren. Denn während der B*-Baum auf skalaren Datentypen arbeitet, auf denen einfach sinnvolle Ordnungen herzustellen sind, arbeitet der R*-Baum mit mehrdimensionalen Objekten (in unserem Fall mit Rechtecken), bei denen mehrere verschiedene Ordnungen denkbar 8 wären. Anschaulich gesehen, faßt der R*-Baum benachbarte Rechtecke (Datenrechtecke) zu neuen übergeordneten Rechtecken (Verzeichnisrechtecke) zusammen (siehe Abblidung 3). y x Abb. 3: Daten- und Verzeichnisrechtecke des R*-Baums Verweise auf Daten befinden sich nur in den Blättern, die sich wie beim B*Baum auf gleicher Höhe befinden. Alle anderen Knoten erhalten Verweise auf die Kindknoten zusammen mit dem Verzeichnisrechteck, das alle Datenrechtecke in dem referenzierten Knoten enthält. Die maximale Anzahl der Einträge pro Knoten wird durch den Parameter M beschränkt, ansonsten wird ein Split-Algorithmus ausgeführt, der den vollen Knoten durch zwei neue ersetzt. Die minimale Anzahl der Einträge pro Knoten wird durch den Parameter m beschränkt, ansonsten wird ein Merge-Algorithmus ausgeführt, der den Inhalt des Knotens auf andere verteilt.Der Parameter p legt die Anzahl der Rechtecke fest, die beim Überlauf eines Knotens aus diesem entfernt und neu in den Baum eingefügt werden. Diese Vorgehensweise wird als forced reinsert, bezeichnet und stellt eine Neuerung des R*-Baums gegenüber dem R-Baum dar. 1.3 Aufbau des R*-Baum-Indexes Erste Überlegungen und Versuche bestanden darin, den R*-Baum als Indexstruktur auf Rechtecken zu erhalten und möglichst wenige Modifikationen vorzunehmen. Ein vorgeschaltetes Modul sollte beim Einfügen von Geometriedaten in den R*Baum (sei es bei der Indexerstellung oder beim nachträglichen Einfügen) die approximierenden Rechtecke vom Typ RECT zu den Objekten vom Typ MDSYS.SDO_ GEOMETRY erzeugen und an den R*-Baum weiterreichen. Da Clipping-Funktion und Approximation ineinaner spielen, und zusammen in einem Durchlauf zu erledigen sind, machte es Sinn, beide Aufgaben in diesem Modul zu vereinen. Die erzeugten Rechtecke sollten in einer extra Spalte der Tabelle zu den Geometrieobjekten abgespeichert werden. Somit blieb sicherzustellen, daß beim Einfügen von Geometriedaten die Spalte mit den Rechteckdaten aktualisiert wurde. Insbesondere beim Anlegen eines Index, mußte dem R*-Baum erst einmal eine Spalte mit Rechteckdaten zur Verfügung gestellt werden. Doch woher sollte die Benachrichtigung kommen, das Veränderungen an der Geometriedatenspalte vorgenommen 9 wurden? Die ODCIIndex-Funktionen des Extensible Indexing Interface werden nur dann aufgerufen, wenn die indexierte Spalte der Tabelle (also die vom Typ RECT) referenziert wird. Eine Überlegung war es, mit Before-Insert-Triggern die Geometrieobjekte vor dem Einfügen abzufangen und mit ihnen das Clipping-Modul aufzurufen, daß die Rechtecke generiert und in die indexierte Spalte schreibt. Dort würden dann automatisch die ODCIIndex-Funktionen von Oracle aufgerufen. Doch dieser Ansatz entpuppte sich als Sackgasse. Schon beim Anlegen des Indexes müssen die Daten innerhalb der Funktion ODCIIndexCreate aus der Tabelle in den R*-Baum eingelesen werden. Doch diese können vorher nicht hineingeschrieben werden, weil schreibene Zugriffe auf die indexierte Spalte einer Tabelle während des ODCIIndexCreate verboten sind. Skripte, die erst die Rechtecke in die Tabelle schreiben bevor der eigentliche Index angelegt wird, wären notwendig gewesen. Dieses schien als höchst unpraktikabel, da der create index-Syntax verloren gegangen wäre. Aufgrund dessen, den ungewissen Laufzeitverhalten der Trigger, und der Tatsache, daß die approximierenden Rechtecke zusammen mit den Originaldaten in einer Tabelle abgespeichert werden müßen, wurde dieser Ansatz nicht weiter verfolgt. Der neue und auch durchgeführete Ansatz ging dahin, den R*-Baum-Index so zu modifizieren, dass er als Indexstrukur auf Daten vom Typ MDSYS.SDO_GEOMETRY arbeitet. Eine genauere Betrachtung ergab, das zwar Veränderungen der ODCIIndex-Funktionen notwendig waren, die internen Funktionen der Baumstruktur zum größten Teil jedoch unverändert bleiben konnten. Sei hier kurz das Arbeitsprinzip des R*-Baum mit dem Clipping-Modul erklärt: O D C I I n R d * - B e a u ( P ( 4 ) l i e s t m R p e a t e b c i m c ) r u f t n t i o n s m a i t m T e a n b a e u l l e n C f l i p p a i n - ( 2 ) l i e s t o m t e i t n ( 3 ) m s a c t i o h r e n i b s r e t A c p h p t e r o c g - M ( C e l l e d ( 1 x a k e k e : ) x t e t e d L r o h T h a - I n Q p c . c r e / S p e C m L A R t e x k x e i T G e G e b a o j e b m o d u l ) e e o k m e t r i e - t e l l e t r i e m d i t a t e n Abb. 4: Aufruf des Clipping-Moduls während des ODCIIndexCreate • Beim Erstellen des Indexes (siehe Abbildung 4) auf eine Spalte vom Typ MDSYS.SDO_GEOMETRY wird das Clipping-Modul aufgerufen, das die Geometriedaten aus der indexierten Tabelle liest, gegebenfalls clippt und die approximerenden Rechtecke zusammen mit der Row-Id des Originalobjekes in eine temporäre Tabelle schreibt. Diese können nun in die Baumstruktur eingelesen werden. • Das Einfügen von Geometrieobjekten funktioniert ähnlich wie beim Anlegen (siehe Abbildung 5), nur daß das Clipping-Modul nun nicht mit der indexier10 ten Tabelle aufgerufen wird, sondern über ODCIIndexInsert zeilenweise mit den neuen Objekten. Die zugeörigen Rechtecke und Row-Ids werden auch hier wieder in eine temporäre Tabelle geschrieben und vom R*-Baum eingelesen. O D C R I I n d * - B a ( P ( 3 ) l i e s t m R A p e p e . c c T h x u L R t e e I n m e - I n / S Q p r o h b c e x i m k ) r u f t a m o a t i o n s i t b G j e k e t o a m u e t r i e f C l i p p i n g ( C - M o d u l ) - e l l e d ( 1 x ) c e k r t : d L t e a t e s m t e i t n ( 2 ) m s a c t i o h r e n i b s r e t A c p h p t e r o c k x i - e Abb. 5: Aufruf des Clipping-Moduls während des ODCIIndexInsert • Das Delete und Update werden vom R*-Baum nicht unterstützt und konnten somit nicht implementiert werden. • Beim Selektieren von Daten liefert der R*-Baum die Row-Ids zurück, dessen zugehörigen Rechecke die Primärfilterbedingung erfüllen. Da dieser aber nicht zwischen geclippten und ungeclippten Objekten unterscheiden kann, kommen einige Row-Ids in der Kandidatenmenge mehrfach vor. Diese müssen, bevor sie an den Sekundärfilter weitergegeben werden, eleminiert werden. Da die Eliminierung eng mit dem Aufruf des Sekundärfilters verknüpft ist, wird die Funktionweise im Kapitel 3.2 auf Seite 19 beschrieben. 1.4 Algorithmen 1.4.1 R*-Baum-Index Der hier erläuterte Index benutzt die Algorithmen des von Sasha Klopp implemetierten R*-Baumes. Dazu gehören für das Einfügen die ChooseSubtree-, die Split-, die Forced-Reinsert-Methode, sowie das Durchlaufen des Baumes für eine SuchAnfrage. Da es größtenteils nur notwendig war, Modifikationen an den ODCIIndexFunktionen des Indexes vorzunehmen, die diese Methoden aufrufen, werden diese hier nicht weiter behandelt und es wird stattdessen auf die Studienarbeit [Klo99] verwiesen. 1.4.2 Berechnen des approxmierenden Rechtecks Zur Berechnung des Approximationsrechtecks werden die Einträge des SDO_ORDI NATES-Arrays eines Geometrieobjektes ausgelesen und die minimalen und maximalen x- und y-Koordinaten gemerkt. Diese bilden dann die zwei gegenüberliegen Eckpunkte des Rechtecks. Liegt ein Objekt von Typ MDSYS.SDO_POLYGON vor, bräuchte theoretisch nur die äußere Umrandung des Polygons betrachtet werden, da die Löcher nicht zur Berechnung des Rechtecks beitragen. Weil nicht sichergestellt werden kann, daß die ersten Einträge im SDO_ORDINATES-Arrays wirklich die Umrandung und die sich an den weiteren Offsets befinden Koordinaten die Löcher bilden, werden trotzdem alle Einträge durchgegangen. 11 1.4.3 Beurteilung der Güte einer Approximation Beim Auslesen des SDO_ORDINATES-Arrays zu Berechnung des approximierenden Rechtecks zu einem Geometerieobjekt von Typ SDO_LINESTRING wird auch die Länge des Streckenzuges berechnet. Das Verhältnis der Länge des Streckenzuges zu der Fläche des Approximationsrechtecks bildet die Grundlage zur Beurteilung der Güte der Approximation, da die Länge des Streckenzuges auch als die Fläche betrachtet werden kann, die dieser in der Ebene einnimmt. Liegt ein Streckenzug waage- oder senkrecht in der Ebene, so wird der Streckenzug durch das Rechteck 100%ig richtig approximiert und die Fläche des Rechtecks stimmt mit der des Streckenzuges überein. Liegt der Streckenzug diagonal in der Ebene, so wird die schecht möglichste Approximation vorgenommen. V erhältnis = F läche Approximationsrechteck Länge Streckenzug Dieses Verhältnis ist im günstigsten Fall 1. Aus der Überlegung, daß das schlechteste Approximationsrechteck zu einem Streckenzug ein Quadrat ist, in dem der Streckenzug diagonal von einem zum gegenüberliegenen Eckpunkt läuft, ergibt sich das Verhältnis für die schlechtetste Approximation: r F läche Approximationsrechteck Schlechtestes V erhältnis = 2 Folgender Wert gibt im reellen Intervall [0,..,1] an, wie groß der Fehler ist, der bei der Approximation gemacht wurde: F ehlerApproximation = V erhältnis Schlechtestes V erhältnis Er ist im besten Fall 0 und im schlechtesten Fall 1. Es kann also unmittelbar die Güte der Approximation von diesem Wert abgeleitet werden. Im folgenden sei mit max_error dieser Wert gemeint. 1.4.4 Clippen von Streckenzügen Um eine möglichst gute Approximation eines Streckenzuges durch ein Rechteck zu erhalten, können Linienzüge mit Hilfe des in RSTClip.c implementierten ClippingModuls geclippt werden. Neben dem Streckenzug werden zwei Parameter benötigt, die der Benutzer festlegt: • max_error gib die oberste Fehlergrenze an, die bei Rechteckapproximation toleriert wird. Dieser Wert kann zwischen 0 (eine 100% genaue Approximation wird verlangt) und 1 (die Approximation kann belibig schlecht ausfallen) liegen. Dieser Wert stellt also die in Kapitel 1.4.3 beschriebende Güte der Approximation dar, die sich der Benutzer wünscht. • min_area gibt einen Minimalwert für den Flächeninhalt des Approximationrechtecks an. Liegt ein Streckenzug derart in der Ebene (z.B. diagonal), sodaß 12 auch durch wiederholtes Clipping der Wert max_error nicht erreicht werden kann, so wird das Clipping abgebrochen, wenn die Rechtecke einen Flächeninhalt von min_area unterschreiten. Beim Aufruf der Clipping-Prozedur ist bereits bekannt, daß der übergebene Streckenzug den Vorgaben nicht gerecht wird. Darum wird der Streckenzug um das letzte Koordinatenpaar verkleinert, in der Hoffnung daß dieser hintere Teil des Streckenzuges ausschlaggebend für die schlechte Approximation war und der vordere Teil die Vorgaben erfüllt. Ist dies nicht der Fall so wird wiederholt der Streckenzug um das letze Koordinatenpaar verkleinert und und Beurteilungen über die entsprechenden Approximationsrechtecke angestellt. Erfüllt dann ein vorderer Teil das Gütekriterium (max_error), so wird das Rechteck für diesen Teil abgespeichert und man muß nur noch den verbleibenden hinteren Teil des Streckenzuges betrachten. Gelangt man aber während dieses Rückwärtshangelns durch den Streckenzug zum ersten Liniensegment, dessen Rechteck auch eine schlechte Approximation bildet, so muß dieses Liniensegment zerteilt werden und es müssen neue Koordinatenpunkte für die Approximationsrechtecke berechnet werden. Diese erfüllen durch wiederholtes Clipping, aufgrund ihres kleiner werdenen Flächeninhaltes, irgendwann zwangsweise das Gütekriterium (min_area). Nachdem das Approximationsrechteck für den vorderen Teil des Streckenzuges abgespeichert wurde, wird sich von dieser Stelle aus vorwärts durch den Streckenzug gehangelt und der jeweils hintere Teil betrachtet. Es wird immer ein Koordinatenpaar zu dem hinteren Teil des Streckenzuges hinzugenommen und das Rechteck berechnet, bis sich durch die neu hinzugenommenen Koordinatenpunkte eine schlechte Approximation ergibt. Dann wird das Approximationsrechteck für den Teilstreckenzug ohne diese Koordinatenpunkte abgespeichert. Von dieser Stelle aus arbeitet der Algorithmus weiter bis zum Ende des Streckenzuges. Besteht der zu betrachtene Teil nur aus einem Liniensegment, und erfüllt dieses die Vorgaben nicht, so wird auch hier, dieses Liniensegment solange zerteilt, bis es dem Kriterium min_area genügt. Algorithmus Clipping_für_Streckenzug /* Durchsucht einen Streckenzug nach Teilstreckenzügen, dessen umgebene Rechtecke eine gute Approximation liefern und speichert diese ab */ laenge:=Anzahl der Koordinatenpaare des Streckenzuges; TeilStreckenzug(start,ende):=Untermenge des Originalstreckenzuges mit dem ersten Koorinatenpaar start und letzen Koordinatenpaar ende; start:=1; ende:=laenge-1; /* Gehe Streckenzug von hinten durch */ WHILE (Approximation(TeiStreckenzug(1,ende))==schlecht) AND (ende>2) DO 13 ende ; END; IF (ende==2) THEN clippe Liniensegment(1,2) und speichere Rechtecke; ELSE speichere Rechteck(1,ende); END IF; /* Gehe Streckenzug von vorne durch */ start:=ende; WHILE (ende <= laenge) DO IF (Approximation(TeiStreckenzug(start,ende)==gut) THEN ende++; ELSE IF (start-ende<2) THEN clippe Linienseqment(start,ende) und speichere Rechtecke; ELSE speichere(start,ende-1); END IF; start=ende; END IF; END WHILE; 14 2 z-Codes 2.1 Motivation In dieser Studienarbeit sollen geometrische Daten indiziert werden. Dazu stellt das Oracle 8i DBMS das Spatial-Paket zur Verfügung. Der in diesem Paket enthaltene Index kann allerdings nur als black-box verwendet werden, so daß bei der Erweiterung des Systems um benutzerdefinierte Funktionen keine wirklichen Voraussagen über das (Laufzeit-)Verhalten gemacht werden können. Daher soll in dieser Studienarbeit ein benutzerdefinierter Index erstellt werden, der alternativ zum OracleSpatial-Index auf den bestehenden Datensätzen und damit gleichen Datentypen eingesetzt werden kann. Zur internen Verwaltung soll der Primärfilter dieses Indextyps auf z-Code-Algorithmen zurückgreifen. 2.2 Prinzip Das Grundprinzip der z-Codes besteht darin, eine gegebene Fläche, in gleichgroße Rechtecke aufzuteilen. Überlegungen zu diesem Thema finden sich unter anderem in [OM84] und [Ore86]. Die Rechtecke entstehen, indem die Fläche in Richtung jeder Koordinatenachse sukzessive halbiert wird. Im Folgenden soll der Begriff z-Rechteck ein Rechteck mit diesen Eigenschaften kennzeichnen. Die sich ergebende Anzahl von Rechtecken ist gleich einer Zweierpotenz. Für einen z-Code typisch ist die Codierung der z-Rechtecke: Im ersten Schritt wird die Fläche in vier Teile geteilt. Diese Teile werden so mit 0 bis 3 (binär: 00 bis 11 je ein Bit für die x- und die y-Achse) numeriert, daß die Numerierung geometrisch einem Z folgt. Die jedem z-Rechteck zugeordnete Nummer entspricht dem z-Code des Rechtecks. Im nächsten Schritt werden diese z-Rechtecke wiederum in Richtung beider Koordinatenachsen halbiert, also jeweils geviertelt. Auch die hierbei entstehenden zRechtecke werden wieder in Form eines Z mit 0 bis 3 codiert, allerdings erhalten sie zusätzlich den Code des Rechtecks, aus dem sie durch Teilung unmittelbar hervorgegangen sind, als Präfix. Dieser Schritt kann beliebig oft wiederholt werden. Mit dieser Konstruktion kann der Ausgangsfläche konsistent die leere Menge also ein Code der Länge 0 zugeordnet werden. • Bis auf drei Ausnahmen gilt, daß z-Rechtecken mit geringem Abstand auch z-Codes mit geringer Differenz zugeordnet sind. Die Ausnahmen betreffen die Übergange zwischen den Vierteln der Ausgangsfläche also von 0[3 . . . ]3 nach 1[0 . . . ]0, von 1[3 . . . ]3 nach 2[0 . . . ]0 und von 2[3 . . . ]3 nach 3[. . . ]0. • Sind a und b z-Rechtecke mit der Eigenschaft, daß a größer ist als b und b von a (vollständig) überdeckt wird, so gilt nach Konstruktion, daß der z-Code von b den z-Code von a als Präfix enthält. Diese Präfixeingenschaft bildet die 15 Grundlage für die nachfolgenden Algorithmen. 0 1 0 0 0 1 1 0 1 0 1 0 0 0 2 0 3 1 2 1 0 0 3 2 3 0 2 1 3 0 3 2 2 2 3 3 2 3 2 3 2 z 2 - C o d e - L ä n g e : 1 , D x = D y = 1 / 2 z - C o d e - L ä n g e : 2 , D x = D y = 1 / 2 ² z 0 2 2 2 - C 2 o 2 d 1 2 2 e 3 ä 0 3 2 n 2 3 g 2 e 1 : 3 3 3 2 3 3 , D 0 2 x = D 3 2 3 1 1 = 3 1 / 2 0 3 1 3 2 1 1 1 1 1 1 3 0 1 3 1 2 3 1 2 y 1 1 0 3 2 3 0 3 1 1 2 3 2 3 2 1 0 0 1 0 1 2 0 3 0 1 0 2 3 3 1 2 2 1 1 1 0 0 1 3 1 2 0 1 1 3 2 2 3 2 - L 0 1 3 3 0 1 1 1 0 2 1 2 1 0 0 3 2 2 0 2 3 0 1 0 0 1 0 3 0 2 1 0 1 2 2 2 0 3 2 0 0 0 1 0 0 2 0 2 0 0 0 2 2 1 0 2 2 0 2 0 1 3 1 1 3 3 0 3 3 3 3 2 3 1 1 3 1 3 0 2 3 3 1 3 3 3 ³ Abb. 6: Aufbau von z-Codes Die so entstehenden z-Codes haben zwei angenehme Eigenschaften. Dieses Prinzip der z-Codes läßt sich im Primärfilter des Index zur Bestimmung einer Kandidatenmenge nutzen. Diese Kandidatenmenge enthält alle Datensätze bis auf diejenigen, die mit Sicherheit nicht zur Ergebnismenge der Anfrage gehören. Beim Erzeugen von z-Codes für ein geometrische Objekt werden ein oder mehrere z-Codes bestimmt, durch die das Objekt repräsentiert wird. Dies geschieht, indem eine Menge von z-Rechtecken bestimmt wird, die das Objekt geeignet überdecken. Dabei stellt sich die Frage, wie genau die Zuordnung von z-Codes zu Objekt sein soll. Wird versucht, das Objekt direkt zu überdecken, kann dies die Anzahl der nötigen z-Rechtecke reduzieren (vgl. Abb.7), bedeutet jedoch einen nicht unerheblichen Rechenaufwand. Dieser Rechenaufwand wird drastisch reduziert, indem nicht das Objekt selbst, sondern das minimale umgebende Rechteck zur Überdeckung mit zRechtecken herangezogen wird. Allerdings kann dies auch zu einer höheren Anzahl von z-Rechtecken führen (vgl. Abb.8). Ein weiterer wichtiger Aspekt ist die Größe der verwendeten z-Rechtecke. Folgende Formen sind denkbar: 1. Das Objekt wird durch genau einen z-Code codiert. Der z-Code läßt sich hierbei leicht bestimmen, indem der z-Code des minimalen z-Rechtecks berechnet wird, welches das gegebene Objekt bzw. dessen umgebendes Rechteck vollständig umschließt. Durch diesen Ansatz werden die Algorithmen einfacher und der interne Verwaltungsaufwand wird minimiert. Allerdings wird dadurch i.a. auch die Kandidatenmenge maximiert, was einen häufigeren Aufruf der Sekundärfilter bewirkt. 2. Das Objekt wird duch eine Menge von z-Codes maximaler Länge codiert. Dazu werden alle z-Rechtecke minimaler Größe bestimmt. Das Objekt wird dann durch die Menge von z-Codes codiert, deren z-Rechtecke vereinigt die minimale Überdeckung des Objektes bzw. dessen umgebenden Recktecks bilden. Auf der einen Seite werden durch diese Vorgehensweise die Algorithmen einfacher und 16 auch die Kandidatenmenge wird minimal, da das Objekt durch die vielen kleinen z-Rechtecke genauer approximiert wird. Auf der anderen Seite wird der interne Verwaltungsaufwand maximal. 3. In einer Mischform wird das Objekt durch eine Menge von unterschiedlich langen z-Codes codiert. Dazu wird ähnlich einem Split-and-Merge-Algorithmus zuerst wie unter 2. die Menge der z-Rechtecke minimaler Größe bestimmt. Danach werden diese z-Rechtecke, soweit möglich zu größeren z-Rechtecke zusammengefasst. Dieses Vorgehen führt zu aufwendigeren Algorithmen. Wie unter 2.) wird durch die gute Approximation die Kandidatenmenge minimal. Der interne Verwaltungsaufwand liegt zwischen den Extrema in 1. und 2.. Diese Form der Codierung entspricht ist vergleichbar mit den Überlegungen in [Ore86]. 0 0 0 0 0 z c o d e = { 0 } z c o d 0 1 1 0 3 2 3 e 2 0 2 1 3 0 0 3 = 0 2 0 { 0 1 2 , 0 1 3 , 0 2 1 , 0 2 3 , 0 3 } z c o d 3 1 2 e 0 0 3 = 1 3 0 { 0 2 0 3 1 3 0 , 3 3 0 , 0 1 0 2 2 0 0 1 3 3 1 0 3 3 , 1 0 , 2 0 1 3 , 2 0 , 2 0 3 3 , 3 } Abb. 7: z-Codes ohne umschreibendes Rechteck 0 z 0 c o d e = { 0 } z c o d 0 3 0 2 1 0 2 3 0 e = 0 1 0 2 1 3 0 0 0 3 0 { 0 0 3 , 0 1 2 , 0 1 3 , 0 2 1 , 0 2 3 , 0 3 } z c o d 3 2 2 e 0 1 3 = { 0 0 0 3 0 Abb. 8: z-Codes mit umschreibendem Rechteck 2 3 0 0 17 1 0 , 0 0 , 1 3 1 0 3 3 0 2 3 3 1 0 3 2 3 , 1 0 , 1 0 3 3 , 2 0 , 2 0 1 3 , 3 0 } 2 3 , 2.3 Algorithmen Ursprünglich war es vorgesehen, den im Abschnitt 2.2 auf Seite 16 unter 3. beschriebenen Ansatz zu verfolgen. Aufgrund der Komplexität der restlichen Implementation, wurde jedoch nur der unter 1. beschriebene Ansatz implementiert. Allerdings ist an den meisten internen Stellen bereits die Verwendung mehrerer z-Codes pro Objekt berücksichtigt. 2.3.1 Erzeugen von z-Codes Gegeben sei das vogegebenen Kartenfenster und das zu codierende Rechteck. Zuerst wird das minimale umgebende Rechteck des Objektes bestimmt. Danach wird der z-Code des minimalen z-Rechtecks bestimmt, welches das eben berechnete umschließende Rechteck vollständig enthält. Algorithmus calc_zcode /* Berechnet einen z-Code mit maximaler Länge max_zcodelen. Das zu codierende Objekt ist durch sein umschreibendes Rechreck rect gegeben. */ IF (rect außerhalb Kartenfenster) THEN error(); IF (size_x(rect) > size_x(Kartenfenster)/2 OR size_y(rect) > size_y(Kartenfenster)/2) THEN EXIT; /* Iterative Berechnung des z-Codes */ FOR depth_cur:=1 TO max_zcodelen DO IF (linkes oberes Viertel) THEN zcode:=zcode+’0’; ELSE IF (linkes unteres Viertel) THEN zcode:=zcode+’2’; ELSE IF (rechtes oberes Viertel) THEN zcode:=zcode+’1’; ELSE IF (rechtes unteres Viertel) THEN zcode:=zcode+’3’; ENDIF Berechne Koordinaten des gewählten Viertels für neues Kartenfenster; IF (size_x(rect) > size_x(Kartenfenster)/2 OR size_y(rect) > size_y(Kartenfenster)/2) THEN EXIT; NEXT; 18 3 Exakte geometrische Operatoren 3.1 Motivation Gemäß des Zwei-Ebenen-Anfrage-Modells lieferen der R*-Baum-Index und der zCode-Index als Primärfilter nur eine Obermenge der Daten, die die eigentliche Anfrage erfüllen können. Diese Kandidatenmege muß nun noch genau untersucht werden, um ein exaktes Ergebinis zu liefern. Dieses geschieht mit Hilfe des Sekundarfilters, der exakten geometrischen Operatoren, die durch den Primärfilter unterstützt werden. 3.2 Aufbau Um dem Umfang der Studienarbeit im üblichen Rahmen zu halten, wurde lediglich der Intersects-Operator implementiert, der überprüft, ob sich zwei Objekte von Typ MDSYS.SDO_GEOMETRY schneiden. Dieser wurde so gestaltet, daß er nicht nur von einem beliebigen Indextyp eingebunden, sondern auch ohne Indexunterstützung benutzt werden kann. Um den beim Aufruf von PL/SQL nach C auftretenden Call-Overhead zu minimieren wurde die Möglichkeit eingeräumt, den Operator über die Hilfsfunktion callOps aus der OCDIIndexFetch-Methode mit den Row-Ids der Kandidatenmenge aufzurufen. Dieser prüft dann, ob sich die zu den Row-Ids gehörenden Geometrieobjekte mit dem mitübergebenden Vergleichsobjekt schneidet. Die Row-Ids der Objekte, die die Sekundärfilterbedingung nicht erfüllen, werden dann durch die Funktion callOps aus der Menge der Row-Ids gestrichen und der ODCIIndexFetch-Methode nicht zurückgegeben. Diese Vorgehensweise findet sich so in der Implementation des R*-Baumes (siehe Abblidung 9). O D C I I n d e x F e t c h ( R * - B a u R V R * - B ( P a L u m / S Q I n L ) d e o e m w ) : - I d r g l e - L i c h o i s t e b j e d e k r t , K O a p n e d i d r a t o a t e r n a n G m e c x R o w - I d - L i s E t e r g d e e b s n e x a k t e e V a l l O p ( C n o e m r g e t r i e l e i c o h s b o j e b j e k t , k t I n s t e O ) t r u e b z w . f a l s e p r s e e r a ( C c t s t o r ) i s Abb. 9: Aufruf des Operators während des ODCIIndexFetch des R*-Baum-Index Alternativ kann der Operator direkt aus PL/SQl oder C mit zwei Geometrieobjekten aufgerufen werden, was aber im Fall von PL/SQL einen größeren CallOverhead zur Folge hat. Der in C implementierte z-Code-Index verfolgt diesen Ansatz (siehe Abbildung 10). O D C I I n - C o d e x F e t c h ( z - C o G d z d e ( C s ) - I n d e x V e e e o r g s m ) : e l e t r i e i c h o s b o b j e j e k k t , t I n t e O t r u e b z w . f a l s e p r s e e r a ( C c t s t o r ) Abb. 10: Aufruf des Operators während des ODCIIndexFetch des z-Code-Index 19 3.3 Algorithmen 3.3.1 Planesweep-Algorithmus Die Überprüfung, ob sich zwei Geometrieobjekte schneiden, erfolgt auf der Basis des in [OW96] beschriebenen Planesweep-Algorithmus. Da dieser von der Annahme ausgeht, daß die Segmente nicht senkrecht in der Ebene liegen, paarweise verschiendene Start- und Endpunkte haben und daß sich maximal 2 Liniensegmente in einem Punkt schneiden, wurden in der eingentlichen Implementation einige Modifikationen vorgenommen, damit dieser mit Geometrieobjekten von Typ SDO_POINT, SDO_LINESTRING und SDO_POLYGON arbeitet. y O b O j e b k j e t k t A B H s S c a n - L i n a l t e t e i g p e u n n d k e t e r i n x - R a e u f - i h e x n f o l g e e Abbildung 11: Das Scan-Line-Prinzip Sei hier die prinzipielle Arbeitesweise des Planesweep-Algorithmus auf Liniensegmenten erklärt (siehe Abbildung 11): Es wird mit einer Scanline in Richtung der x-Achse über die Ebene gefahren, in der die Objekte liegen. Dabei trift die Scanline auf die Start- und Endpunkte der Liniensegmente, die Haltepunkte. Diese werden vorher der Größe nach sortiert, in die Q-Liste (Priorty-Queue) eingetragen. Trifft die Scanline auf einen Startpunkt, so wird überprüft, ob sich das zu dem Haltepunkt gehörige Liniensegment mit einem bereits in der Vergleichsliste, der L-Liste, eingetragenen Liniensegment schneidet. Ist dies der Fall, so kann der Algorithmus mit TRUE beendet werden und man ist fertig. Ansonsten wird das Liniensegment auch in die L-Liste eingetragen. Handelt es sich bei dem Haltepunkt um einen Endpunkt, so wird das zugehörige Liniensegment aus der L-Liste gelöscht. Wenn der letze Haltepunkt in der Q-Liste abgearbeitet wurde und vorher kein Schnitt fertiggestellt wurde, wird der Algorithmus mit FALSE verlassen. 20 Algorithmus Planesweep /* Liefert zu einer Menge S = {s1 , ...sN } von Liniensegmenten in der Ebene true, falls es ein Paar sich schneidener Segmente in S gibt und false sonst.*/ Q:=Folge der 2N Anfangs und Endpunkte von Elementen in S in aufsteigender x-Reihenfolge; L:=NIL; /* Menge der jeweils aktiven Liniensegmente */ gefunden:=false; WHILE (Q ist nicht leer) AND NOT gefunden DO p:= nächster Haltepunkt von Q; IF p ist Anfangspunkt eines Segments s THEN IF s schneidet ein Element aus L THEN RETURN TRUE; ELSE füge s in in L ein END IF; ELSE /* p ist Endpunkt eines Segmentes */ entferne s aus L; END IF; END; RETURN FALSE; 3.3.2 Schnitt zweier Liniensegmente Zur Schnittüberprüfung zweier Liniensegmente werden zunächst die Geradengleichungen der Linien berechnet. Diese werden dann gleichgesetzt und der Schnittpunkt berechnet. Liegt der x-Wert des Schnittpunktes zwischen oder auf den Startund Endpunkten der Liniensegmente so liegt ein Schnitt vor, ansonsten nicht. Sollte eines der Liniensegmente senkrecht in der Ebene liegen, so wird die Ebene um 90◦ gedreht, bevor die Geradengleichungen berechnet werden. Objekte vom Typ SDO_POINT werden als waagerechte Liniensegmente mit identischen Start-und Endpunkten aufgefaßt. Liegt ein Segment waagerecht und ein Segment senkrecht in der Ebene, so erübrigt sich die Berechnung der Geradengleichungen und der Schnittpunkt kann direkt mittels der Koordinaten bestimmt werden. 3.3.3 Schnittprüfung für Polygon mit Loch Für Objekte von Typ SDO_POLYGON müssen noch weitere Prüfungen erfolgen, sollte der Planesweep-Algorithmus false zurückgeliefert haben. Es muß sichergestellt werden, das ein Objekt nicht vollständig in einem Polygon enthalten ist, denn dann läge eventuell doch ein Schnitt vor. Ist dies der Fall, bleibt zu prüfen ob dieses enthaltene Objekt in einem Loch eines umgebenden Polygons liegt, womit wiederum 21 kein Schnitt vorläge (Abbildung 12). O b j e k t B s c h n e i d e t O b j e k t A O A b j e k t B s c h n e i d e t O b j e k t A n i c h t : A A B B B Abbildung 12: Spezialfall SDO_Polygon des Intersects-Operators Während es bei konvexen Objekten ausreichen würde die umgebenden Rechtecke der Objekte zu betrachten, so müssen bei konkaven Objekten komplizierte Algorithmen angewandt werden. Da durch die Schnittprüfung bekannt ist, daß ein Objekt sich entweder gänzlich außerhalb oder innerhalb des Polygons befindet, reicht es aus zu überprüfen, ob ein beliebiger einzelner Punkt dieses Objektes im Polygon liegt. Für dieses Problem empfiehlt sich der Algorithmus Punkt in Polygon, der genau dieses überprüft: Es wird ein Strahl in beliebiger Richtung von einem Punkt des Objektes ausgesandt und die Schnitte dieses Strahls mit den Kanten des Polygons gezählt. Ist die Anzahl ungerade, so befindet sich das Objekt innerhalb des Polygons, aber ausserhalb eines Loches. Ist die Anzahl der Schnitte gerade, so befindet es sich ausserhalb des Polygons oder innerhalb eines Loches. In der Implementation könnte der Strahl einfach durch ein Liniensegment vom Ursprung der Ebene (0,0) zum ersten Punkt des Geometrieobjektes, von dem angenommen wird, daß es in einem Polygon liegt, simuliert werden (siehe Abbildung 13). Die Schittpunktzählung könnte mit Hilfe des (angepaßten) Planesweep-Algorithmus vorgenommen werden, der als Parameter für die Q-Liste das Polygon und das Liniensegment, das den Strahl darstellt, übergeben bekommt. x x x A A A B B B ( 0 , 0 ) y ( 0 , 0 y ) ( 0 , 0 ) y Abbildung 13: Einige Beispiele zum Punkt in Polygon-Algorithmus Leider konnte der Punkt in Polygon-Algorithmus nicht mehr in den IntersectsOperator einbaut werden, sodaß dieser nur eine reine Schnittüberprüfung vornimmt. 22 Teil II Oracle 8i 4 Extensible Indexing Interface Werden neue Datentypen in Oracle durch den Benutzer eingeführt, so ergibt sich schnell die Anforderung nach einem Index auf diesen Datentyp. Darum stellt Oracle 8i ein Konzept namens Extensible Indexing Interface bereit, mit dessen Hilfe ein neuer Indextyp geschaffen werden kann, den man auf die entsprechenden Bedürfnisse des neuen Datentyps abstimmen kann. Dieses Konzept ist Teil der objektrelationalen Fähigkeiten, die es erlauben, sogenannte Data Cartridges [BK00] zu definieren. Darunter versteht man eine Einheit von neuen Datentypen, zugehörigen Indextypen und, bei Bedarf, auch Erweiterungen des Anfrage-Optimierers. Für einen neuen Indextyp muß das Extensible Indexing Interface implementiert werden, indem eine Klasse definiert wird, die bestimmte Methoden bereithält. Weiterhin müssen Operatoren definiert werden, die auf dem neuen Datentyp arbeiten und für die der Indextyp geschaffen wurde. Wenn in der where-Klausel einer selectAnfrage einer dieser Operatoren benutzt wird und als erstes Argument eine mit dem passenden Indextyp indexierte Spalte benutzt wird, wählt der Optimierer den Zugriffspfad über diesen Index. Im folgenden werden die wichtigsten zu implementierenden Methoden aufgeführt. Bis auf ODCIGetInterfaces bekommen alle statischen Methoden einen Parameter vom Typ sys.ODCIIndexinfo übergeben. Diese vordefinierte Struktur enthält alle notwendigen Informationen über die indexierte Spalte. Außerdem geben alle ODCIIndex-Funktionen eine Zahl zurück. Diese muß im Erfolgsfall die Konstante ODCIConst.Success sein und sonst ODCI-Const.Error. Die Methoden müssen zum Teil mit Argumenten von einem implementationsabhängigen Typ definiert werden. Es ist <icoltype> der Typ, der mit diesem Indextyp indexiert werden kann, <impltype> ist der Name der Klasse, die dieses Interface implementiert und <opbndtype> ist der Typ des Rückgabewerts des Operators. 4.1 ODCIGetInterfaces static function ODCIGetInterfaces ( ifclist out sys.ODCIObjectList ) return number Beim Erzeugen eines Indextyps mit create indextype ruft Oracle diese Funktion auf, um zu überprüfen, ob das geforderte Interface implementiert wurde. Alle von dieser Klasse implementierten Interfaces müssen in ifclist deklariert werden. 4.2 ODCIIndexCreate static function ODCIIndexCreate( ia sys.odciindexinfo, parms varchar2 ) return number 23 Wenn ein Index dieses Typs erstellt werden soll, wird diese Methode von Oracle aufgerufen. Mit erfolgreicher Ausführung muss ein funktionsfähiger Index auf der indexierten Spalte bestehen. Insbesondere müssen alle Daten bereits vom Index eingelesen worden sein. 4.3 ODCIIndexInsert static function ODCIIndexInsert ( ia sys.odciindexinfo, rid varchar2, newval <icoltype> return number Beim Einfügen von Daten in eine mit diesem Indextypen indexierte Spalte wird diese Methode aufgerufen, die jedes Datum einzeln in den Index einfügt. Sie bekommt u. a. das Datum und die zugehörige Rowid im Zeichenkettenformat übergeben. 4.4 ODCIIndexAlter static function ODCIIndexAlter( ia sys.odciindexinfo, parms varchar2, alter_option number ) return number Mit dieser Methode kann eine Änderung der Indexparameter bearbeitet oder der Name des Indexes geändert werden. 4.5 ODCIIndexUpdate static function ODCIIndexUpdate( ia sys.odciindexinfo, rid varchar2, oldval <icoltype> newval <icoltype> ) return number Ein Update einer indexierten Tabelle bewirkt für jede geänderte Zeile den Aufruf dieser Methode. 4.6 ODCIIndexDelete static function ODCIIndexDelete( ia sys.odciindexinfo, rid varchar2, oldval <icoltype> return number Diese Methode wird von Oracle aufgerufen, wenn ein Datum aus dem Index gelöscht werden soll. 24 4.7 ODCIIndexTruncate static function ODCIIndexTruncate( ia sys.odciindexinfo ) return number Es besteht die Möglichkeit, eine Tabelle vollständig zu leeren, ohne einen deleteBefehl ohne where-Klausel zu benutzen, nämlich mit dem truncate table-Befehl. Es wird diese Methode aufgerufen, die den Index auf dieser Spalte leeren muß. 4.8 ODCIIndexStart static function ODCIIndexStart( sctx in out <impltype>, ia sys.odciindexinfo, op sys.odciPredInfo, qi sys.ODCIQueryInfo, strt <opbndtype>, stop <opbndtype>, in <valargs> ) return number Bevor die Daten mit Hilfe des Indexes geholt werden, wird er der Index dieser Methode initialisiert. Im wesentlichen werden der Operatorname op und die restlichen Argumente des Operatoraufrufs valargs übergeben. Es wird eine Instanz der Klasse erzeugt, die dieses Interface implementiert. Mit Hilfe dieser Instanz, die als eine Art Cursor betrachtet werden kann, und der ODCIIndexFetch-Methode wird die Anfrage von der Datenbank abgearbeitet. Falls es Operatoren gibt, die einen unterschiedlichen Rückgabewert und/oder unterschiedliche Parameterlisten besitzen, muss für jede Kombination eine eigene ODCIIndexStart-Funktion geschrieben werden. Operatoren mit verschiedenen Namen, aber gleichen Parameterlisten und Rückgabetypen benötigen keine unterschiedlichen Startmethoden. 4.9 ODCIIndexFetch member function ODCIIndexFetch( self in out <impltype>, nrows number, rids out sys.odciridlist ) return number Nachdem mit ODCIIndexStart ein Cursor vorbereitet wurde, kann mit dieser Methode eine gewisse Anzahl von Ergebnissen geholt werden. Diese Anzahl wird in dem Parameter nrows übergeben. Während die bisherige Methoden alle als statisch definiert werden müssen, sind diese und die ODCIIndexClose-Methode Instanzenmethoden, da sie auf einem bestimmten Cursor, also einer Instanz arbeiten, der zur Laufzeit zur Verfügung stehen muß. Die Deklaration des self-Parameters als in out ist notwendig, da diese Klassenfunktion den Inhalt dieser Klasse ändern können muß. Standardmäßig können nur Klassenprozeduren den Klasseninhalt ändern, nicht aber Klassenfunktionen. 25 4.10 ODCIIndexClose member function ODCIIndexClose return number Nach Beendigung der Anfragebearbeitung wird mit dieser Methode der Cursor geschlossen. Danach ist diese Instanz nicht mehr zu gebrauchen und kann gelöscht werden. Falls z. B. die Implementation mit Hilfe einer externen Bibliothek durchgeführt wurde, können hier nicht mehr benötigte Ressourcen freigegeben werden. 4.11 ODCIIndexDrop static function ODCIIndexDrop( ia sys.ODCIindexinfo ) return number Wird ein benutzerdefinierter Index nicht mehr benötigt, so kann er mit dem drop index-Befehl gelöscht werden. Dann wird diese Methode aufgerufen, um eventuelle Aufräumarbeiten zu leisten, z.B. um die Indextabellen, temporäre Tabellen und Metadatentabellen zu löschen. 5 Oracle Call Interface Das Oracle Call Interface (OCI) ist eine Programmierschnittstelle, die es ermöglicht Programme, Funktionen und Prozeduren in einer Hochsprache wie etwa C zu implementieren, welche auf das Oracle-System zugreifen. Diese API ist recht komplex, da sie den gesamten Funktionsumfang des Oracle-Systems zur Verfügung stellt (vgl. Oracle Online-Dokumentation: Oracle8i Server Application Development, Release 8.1.6 → Language and Interface Documentation → Oracle Call Interface Programmer’s Guide ). Aufgrund des Umfangs von OCI sollen hier nur einige Grundlagen dargestellt werden. 5.1 HandleKonzept Eines der Grundprinzipien von OCI-Programmen ist der Verzicht auf statische Variablen. Daher werden Informationen über bestehende Verbinungen, Benutzer, Fehler, etc. in Handles gesichert. Diese Handles sind hierarchisch angeordnet. In stand-alone Programmen, die über OCI mit Oracle kommunizieren, werden die wichtigsten Handles beim Anmelden an das Oracle-System initialisiert. Weitere Handles, können dann mit OCI-Aufrufen aus diesen elementaren Handles gewonnen werden. In Shared Libraries (vgl. 6 auf der nächsten Seite) wird den eigenen Funktionen von Oracle ein OciExtProcContext-Handle übergeben, aus dem einmal die restlichen Handles gewonnen werden können. HINWEIS: Diese Extraktion über OCIExtProcGetEnv funktioniert scheinbar nur einmal pro Aufruf der Shared Library. Weitere Aufrufe liefern ohne Fehlermeldung(!) nur unsinnige Werte. Die erzeugten Handles müssen daher allen eigenen Funktionen mitübergeben werden. 26 5.2 Statements SQL-Statements können in OCI-Programmen einfach ausgeführt werden. Dazu wird zuerst ein String mit dem SQL-Statement und eventuellen Platzhaltern erzeugt. Mit diesem wird eine Statement-Handle initialisiert. Danach werden bei Bedarf Variablen an die Platzhalter gebunden (lesend: define, schreibend: bind). Zuletzt wird das Statement via OciStmtExecute ausgeführt. Sollen mehrere Datensätze gelesen werden, kann dies durch mehrmaligen Aufruf von OciFetch umgesetzt werden. 5.3 Speicherverwaltung Für Shared Libraries (vgl. 6) bietet OCI eine eigene Speicherverwaltung an. Neben der Größe des zu reservierenden Speichers kann hier beim Reservieren auch eine Gültigkeitsdauer angegeben werden, d.h. es ist möglich Speicher automatisch von Oracle wieder freigeben zu lassen. HINWEIS: Nach unseren Erfahrungen erfordert der Umgang mit über OCI-Funktionen reserviertem Speicher besondere Sorgfalt. Die Überprüfung von unberechtigten Zugriffen z.B. auf fremde Speicherbereiche wird - im Gegensatz zu Linux - sehr vernachlässigt, was leicht zu unbeabsichtigten Ergebnissen und Abstürzen führen kann. 6 Oracle Shared Library Um Oracle durch in einer Hochsprache wie C geschriebene Funktionen, Prozduren, Indextypen, etc. zu erweitern, müssen diese, damit sie auf dem Oracle-Server ausgeführt werden können, in Bibliotheken (Shared Libraries) abgelegt werden. Dazu werden die Programme normal übersetzt und dann z.B. mit gcc -shared zu einer Shared Library gelinkt. Dabei ist zu beachten, daß der entstehende Maschinencode auf dem Oracle-Server ausführbar sein muß. Die Funktionen und Prozeduren der Shared Library werden von Oracle über einen External Procedure Listener mit RPC-Techniken aufgerufen. 27 Teil III Implementation 7 R*-Baum Folgende Kapitel beschreiben die in PL/SQL durchgeführte Implementation des R*-Baum-Index, sowie einige Skripte die in PL/SQL erstellt wurden. Das ClippingModul wurde, da es Operationen auf den Geometriedaten vornimmt, in C implementiert und wird in Kapitel 7.3 auf Seite 31 beschrieben. 7.1 PL/SQL 7.1.1 RSTConf.sql Diese Datei enthält lediglich Anmeldung der Shared Libraries mit den entsprechenen Pfaden, die der Benutzer hier anpassen kann. 7.1.2 RSTPath.sql Beim Einfügen eines neuen Eintrags in den R*-Baum berechnet eine Methode das optimale Blatt zum Einfügen. Damit in der R*-Baum Tabelle keine Verweise auf den Vaterknoten nötig sind und um Zugriffe auf die Tabelle gering zu halten, wird der Pfad zu diesem Blatt in einer Struktur gehalten. Bei einer Anfrage wird ebenfalls ein Pfad durch den Baum benötigt. Beide Strukturen sind in dieser Datei definiert. Diese Datei ist mit der Originaldatei identisch, da keine Veränderungen beim Anpassen des R*-Baumes vorgenommen werden mußten. 7.1.3 RSTTreetable.sql Die Datei RSTreetable.sql enthält die Implementation der Klasse treetable. Diese stellt die Repräsentation eines Baumes dar, der in einer Tabelle abgespeichert wird, zusammen mit den notwendigen Hilfs- und Pflegeprozeduren zur Verwaltung des Baumes. Diese Datei konnte ohne Veränderungen übernommen werden. 7.1.4 RSTSupp.sql In dieser Datei werden einige Hilfsklassen, wie zum Beispiel die Klasse rectpointer implementiert, die ein Verzeichnisrechteck mit dem Verweis auf einen Kindknoten verschmilzt. Hinzugefügt wurde der Typ ridtable, der ein Nested-Table von RowIds im Character-Format (VARCHAR2(18)) darstellt. Dort werden später bei einer Anfrage die Row-IDs der Kandidaten für das Anfrageergebnis gespeichert. 7.1.5 RSTRectangle.sql In der Datei wurden die Klassen point und rect mit den Methoden contains, intersects und iscontainedby implementiert. Da Operatoren nicht mit mit Hilfe von Klassenmethoden, sondern nur mit Funktionen definiert werden können, gibt 28 es in der Datei RSTOps.sql noch drei Dummy-Funktionen, die diese Methoden benutzen. Da diese Dummy-Funktionen den Methoden zwischengeschaltet sind, waren auch hier keine Modifikation vorzumehmen. 7.1.6 RSTOps.sql Diese Datei enthält die Dummy-Funktionen, die die in der Klasse rect implementierten Methoden intersects, contains und iscontainedby aufrufen. Des weiteren werden die Operatoren deklariert. Im wesentlichen wurde eine Funktion getRect hinzugefügt, die von den einzelnen Dummy-Funktionen aufgerufen wird. Diese berechnet von einen übergebenen Geometrieobjekt ein Approximationrechteck, das von den oben genannten Methoden benötigt wird. 7.1.7 RSTIndex.sql Zur Umstellung des R*-Baum-Indexes auf Objekte von Typ SDO_GEOMETRY und zur Vorschaltung des Clipping-Moduls mußte die Implementation an einigen Stellen modifiziert werden. • Eingabe der Parameter Bevor der eingentliche Indextyp angelegt wird, werden die benötigten Parameter mit Hilfe des ACCEPTS-Befehls vom Benutzer eingegeben. Dazu gehört der maximale Wert für die Güte der Approximation (max_error), sowie die minimale Fläche des Approximationsrechtecks, bei der der Clippingalgorithmus noch aufgerufen werden soll (min_area). Außerdem müssen die vom R*-Baum benötigten Paramter eingegeben werden (maximale Knotengröße, minimale Knotengröße und der Parameter p). • Anmelden der Shared Libaries Ebenfalls vorher geschieht das Anmelden der Shared Libaries RSTClip.so, das die Implementation des Clipping-Moduls enthält, und RSTOps.c, in der der Intersects-Operator auf Geometriedaten von Typ SDO_GEOMETRY implementiert wurde. • Anmelden der C-Routinen Um die in C implementierten Routinen benutzen zu können, müssen diese noch bei Oracle angemeldet und bekannt gegeben werden, in welchen Bibliotheksdateien sie sich befinden. • static function ODCIIndexCreate( ia sys.ODCIindexinfo, parms VARCHAR2 ) return number Ruft die Funktion createRects auf, welche alle Geometriedaten der indexierten Tabelle einliest, wenn notwenig gemäß den Parametern clippt und das Approximationsrechteck und die Rowid des Originalobjektes in eine temporäre Tabelle schreibt. Dann wird ein Treetable-Object erzeugt, das den Baum anlegen lässt. Es wird ein Cursor erstellt, mit dem nun alle Approximationsrechtecke aus der temporären Tabelle gelesen und mit Hilfe der Funktion IndexInsertRS in den R*-Baum eingefügt werden. 29 • static function ODCIIndexDrop( ia sys.ODCIindexinfo ) return number Löscht lediglich den Baum und die temporäre Tabelle. • static function ODCIIndexInsert( ia sys.ODCIindexinfo, rid VARCHAR2, newval MDSYS.SDO_GEOMETRY ) return number Ruft die C-Funktion insertRects mit dem neuen Geometrieobjekt newval auf und clippt dieses, wenn notwendig. Die Approximationsrechtecke werden zusammen mit den Row-Ids in eine temporäre Tabelle geschrieben. Auf dieser Tabelle wird ein Cursor erstellt der die Rechtecke liest und mit Hilfe der Funktion IndexInsertRS in den Baum einfügt. • static function IndexInsertRS( ia sys.ODCIindexinfo, rid VARCHAR2, newval RECT ) return number Fügt das übergebene Datenrechteck newval in den R*-Baum ein, nachdem es normiert wurde. • static function ODCIIndexStart( sctx IN OUT &indextypename._im, ia sys.ODCIindexinfo, op sys.ODCIPredInfo, qi sys.ODCIQueryInfo, strt NUMBER, stop NUMBER, cmpval MDSYS.SDO_GEOMETRY ) return number Berechnet das Approximationrechteck zu dem Vergleichsobject cmpval und speichert es in der Variable cmpRect. Es wird unter anderem ein Objekt von Typ ridtable und eine Indexvariable in den Scan-Context geschrieben, die beim ODCIIndexFetch benötigt werden. • member function ODCIIndexFetch( self in OUT &indextypename._im, nrows NUMBER, rids OUT sys.ODCIridlist ) return number Beim ersten Aufruf der Funktion ODCIIndexFetch (rstidx ist gleich 1) werden alle Ergebnisse aus dem R*-Baum gelesen und in den Nested-Table rst_ rids geschrieben. Es befinden sich nun alle Row-Ids in der Tabelle, die den Primärfilter erfüllen. Die Funktion callOps wird mit dieser Tabelle aufgerufen. Dort werden alle Row-Ids mit Heap-Sort sortiert und der Reihe nach an die Funktion intersects übergeben, die den Sekundär-Filter darstellt. Row-Ids, dessen zugehörigen Geometrieobjekte den Operator nicht entsprechen, oder mehrfach vorhanden sind, werden aus der Tabelle entfernt. Diese Tabelle, die nun nur noch die Row-Ids enthält, die dem exakten Ergbnis entsprechen, wird an PL/SQL zurückgegeben. Nun wird die Ergebnismenge zu Teilen von jeweils 30 nrows Row-Ids in die Collection rids vom Typ sys.ODCIridList geschrieben. Werden weniger als nrows Rowids in rids geschrieben, so wird ODCIIndexFetch nicht nochmal von Oracle aufgerufen. 7.1.8 RSTRemoveIndextype.sql Mit Hilfe dieser Skriptdatei ist es möglich angelegte R*-Baum-Indextypen zu löschen. 7.1.9 RSTRemoveAll.sql Mit Hilfe dieser Skriptdatei ist es möglich die gesamte R*-Baum-Index Implementation wieder aus dem Oracle-DBMS zu löschen. 7.2 C-Modul RSTSupp In diesem C-Modul sind zwei Hilfsprozeduren implementiert. Die Prozedur getDBHand les soll die Parameterübergabe der Datenbank-Handles erleichtern, da es nur einmal möglich ist, diese mit OCIExtProcGetEnv aus dem Contex-Handle zu gewinnen. Die Procedur checkErr soll eventuelle Fehler abfangen. 7.2.1 GetDBHandles void getDBHandles( OCIExtProcContext *ctx, dbhandles *dbhp ) Das Environment- , Service Context- und Errorhandle werden mit OCIExtProcGetEnv aus dem Context-Handle gelesen und die Struktur dbhp vom Typ dbhandles zusammen mit User Session Handle geschrieben. So können sie in kompakter Form den einzelnen Routinen übergeben werden. 7.2.2 checkErr void checkErr( dbhandles dbhp, char *myerror, sword status ) Die meisten OCI-Funktionen des Oracle Call Interface liefern einen Statuswert zurück, der angibt, ob die durchzuführende Aktion erfolgreich war. Mit diesem Status status wird die Prozedur checkErr aufgerufen, um im Falle eines Fehlers die zugehörige Fehlermeldung zu bestimmen und mit Hilfe von OCIExtProcRaiseExcpWithMsg auszugeben. Im Buffer myerror können Debug-Informationen übergeben werden, die zum Beispiel die Stelle im Code beschreiben, an der der Fehler auftritt. Diese Beschreibungen können durch Setzen des Präprozessormakros debug in den Quellcode bei der Kompilation eingebunden werden. 7.3 C-Modul RSTClip In der Datei RSTClip.c ist das Clipping-Modul des R*-Baums implementiert. Es hat die Aufgabe die Geometriedaten aus der Tabelle zu lesen, das approximierende 31 Rechteck zu berechnen und gegebenenfalls, Linienzüge zu clippen. Die Approximationsrechtecke werden dann zusammen mit der Row-Id des Originalobjektes in eine temporären Tabelle geschrieben und dem R*-Baum zur Verfügung gestellt. 7.3.1 createRects void createRects( OCIExtProcContext *ctx, char *tablename, char *temptable, OCINumber *oci_max_error, OCINumber *oci_min_area ) Diese Prozedur wird während ODCIIndexCreate aus PL/SQL aufgerufen. Sie erhält neben den Service Context den Tabellennamen tablename der indexierten Tabelle, den Namen temptable der temporären Tabelle, in der die Rechtecke und Row-Ids abgespeichert werden sollen, sowie die Parameter oci_max_error und oci_min_area im OCINumber-Format, die der in Kapitel 1.4.4 auf Seite 12 beschriebene Clipping-Algorithmus benötigt. Es werden mit einer Select-Anfrage die Geometriedaten aus der Tabelle in das Objekt geo vom Typ sdo_geometry geschrieben. Gemäß des Geometrietyps werden die Funktionen getType1Values, getType2Values und getType3 Values aufgerufen, die das approximierende Rechteck berechnen. Im Fall des Typs SDO_LINESTRING liefert die Funktion getType2Values einen Wert zurück, der die Güte der Approximation beschreibt. Liegt dieser unterhalb der vom Benutzer angegeben Schwelle (max_error in PL/SQL) so wird das Rechteck mit der Row-Id in die temporäre Tabelle gschrieben und das nächste Geometrieobjekt geholt. Liegt es oberhalb des Schwellwertes, so werden die Koordinaten des Geometrieobjektes zum Clippen an die Prozedur ClipLineString weitergegeben. Die Geometrietypen SDO_COLLECTION, SDO_MULTIPOINT, SDO_MULTILINE STRING, SDO_MULTIPOLYGON (Typen 4-7) werden wie auch SDO_UNKNOWN (Typ 0) nicht unterstützt. Weiterhin wird ein Insert-Statement vorbereitet, um das approximierende Rechteckt und die zugehörige Row-Ids in die temporären Tabelle zu schreiben. 7.3.2 insertRects void insertRects( OCIExtProcContext *ctx, char *temptable, sdo_geometry *geo, sdo_geometry_ind *geo_ind, OCIType *geom_tdo, char *orid, OCINumber *oci_max_error, OCINumber *oci_min_area ) Diese Prozedur wird während ODCIIndexInsert aus PL/SQL aufgerufen und arbeitet prinzipiell auf die gleiche Weise wie die Prozedur createRects. Hier werden jedoch nicht die Geometriedaten aus der Tabelle gelesen, sondern es wird, da das Einfügen in ODCIIndexInsert zeilenweise geschieht, das Geometrieobjekt direkt aus 32 PL/SQL im Parameter geo übergeben. Die Parameter geo_ind und geom_tdo werden in der Prozedur nicht benötigt, müssen aber beim Funktionsaufruf mit übergegeben werden. Bei geo_ind handelt es sich um eine Indikatorvariable und bei geom_tdo um das Type-Desriptor-Object zu geo. 7.3.3 getType1Values void getType1Values( dbhandles dbhp, sdo_geometry *geo, rectangle *rect ) Handelt es sich beim Geometrieobjekt um den Typ SDO_POINT, so werden die x- und y-Koordinaten ausgelesen und in das Objekt rect von Typ rectangle geschrieben, welches das Approximationsrechteck darstellt. Dieses wird dann in der Prozedur createRects bzw. insertRects in die temporäre Tabelle geschrieben. 7.3.4 getType2Values double getType2Values( dbhandles dbhp, sdo_geometry *geo, int_point_type *coordinates, rectangle *rect, double max_error, int min_area ) Ist das Geometrieobjekt geo vom Typ SDO_LINESTRING, so werden alle Koordinaten aus dem SDO_ORDINATES-Array gelesen und in das Array coordinates vom Typ int_point_type geschrieben. Zudem wird das approximierende Rechteck berechnet und in der Struktur rect vom Typ rectangle gespeichert. Es wird gemäß der in Kapitel 1.4.3 auf Seite 12 beschriebenen Überlegungen der Verhältniswert max_error berechnet, der aussagt, ob es sich um eine auschreichend gute Approximation handelt, und zurückgegeben. 7.3.5 getType3Values void getType3Values( dbhandles dbhp, sdo_geometry *geo, rectangle *rect ) Für den Typ SDO_POLYGON werden alle Koordinaten aus dem SDO_ORDINATES-Array gelesen, das approxmierende Rechteck berechnet und in der Structur rect vom Typ rectangle abgespeichert. 7.3.6 clipLineString void clipLineString( dbhandles dbhp, int_point_type *coordinates, int no_coordinates, rectangle *rect, double max_error, 33 int min_area, OCIStmt *i_stmthp ) Hier findet der Clipping-Prozeß statt. Die Prozedur wird mit dem coordinatesArray aufgerufen, in dem die Koordinaten des Streckenzuges gespeichert sind. Der Streckenzug wird gemäß des in Kapitel 1.4.4 auf Seite 12 beschriebenen Algorithmus geclippt. Die einzelnen Rechtecke werden zusammen mit der zugehörigen RowId mittels der Funktion insertRect in die temporäre Tabelle abgespeichert. Zwei Unterroutinen werde für das Clippen benutzt: • double getLineStringrect( int start, int end ) Berechnet das approximierende Rechteck zu einem Teil des Streckenzuges, der mit dem Eintrag start des coordinates-Arrays beginnt und mit dem Eintrag end endet, und gibt dieses zurück. • void clipLineStringHard( int x1, int y1, int x2, int y2 ) Besteht die Notwendigkeit ein Liniensegment des Streckenzuges zu Clippen, zu wird Prozedur clipLineStringHard solange rekursiv aufgerufen, bis die entstandenen Teilstücke des Liniensegmentes den Kriterien genügen. 7.3.7 insertRect void insertRect( dbhandles dbhp, rectangle *rect, int min_x, int min_y, int max_x, int max_y, OCIStmt *i_stmthp ) Wird von ClipLineString aufgerufen, wenn ein Approximationsrechteck in die temporäre Tabelle geschrieben werden soll. Die Koordinaten min_x, min_y, max_x, max_y bilden dabei zwei Eckpunkte des Rechtecks und werden in das Objekt rect von Typ rectangle geschrieben. Dieses wird dann mit der zugehörigen Row-Id mit Hilfe des vorbereiteten Insert-Statement i_stmthp in die temporäre Tabelle geschrieben. 34 8 z-Codes 8.1 PL/SQL Bei der Implementation des z-Code-Index wurde darauf geachtet, möglichst alle Routinen in der Programmiersprache C zu implementieren. Trotzdem wurden einige Teile in PL/SQL umgesetzt, da der Aufwand einer Implementation in C unverhältnismäßig hoch gewesen wäre, und dies auch die Performance nicht beeinflußt. Auf diese Weise sind drei PL/SQL-Skriptdateien entstanden. 8.1.1 zcode index.sql Diese Skriptdatei enthält die nötigen Anweisungen zur Erzeugung eines Indextyps. Zuerst werden die in einer shared library zusammengefassten C-Funktionen und Prozeduren angemeldet. Danach folgt die Deklaration des z-code-Indextyps. Zuletzt enthält die Datei den Implementationsteil des Indextyps. Dieser setzt sich praktisch nur aus Aufrufen der Shared Library zusammen. Lediglich einige vorbereitende Aufgaben wie z.B. das Anlegen oder Löschen von Tabellen wurde in PL/SQL umgesetzt. 8.1.2 zcodelib.sql Dieses Skript wird von zcode_index.sql aufgerufen und gibt die shared library dem System bekannt. Dies wurde nur zu leichteren Installation und Anpassung in eine weitere Datei ausgelagert. 8.1.3 zcode ops.sql In diesem Skript werden die Index-Operatoren deklariert und durch Aufrufe der shared library implementiert. Das Skript wird ebenfalls von zcode_index.sql aufgerufen. 35 8.2 Tabellen Der implementierte z-Code-Index nutzt zwei unterschiedlich Tabellen. • ZCODE_METADATA In dieser Tabelle verwaltet der Index die Metadaten aller Indexe. Dazu wird für jeden Index ein Eintrag mit wichtigen Parametern des Index erstellt. Jeder Eintrag kann anhand der Schlüsselwerte indexname und tablename identifiziert werden. Falls nötig wird die Tabelle beim Anlegen eines neuen z-CodeIndex erzeugt. I I N D E X N A M E T A B L E N A M E Z C O D E T A B L E N A M E Z C O D E L E N M A X X M A X Y M I N X M I N Y Abb. 14: Metadatentabelle zcode_metadatatable des z-Code-Index • Z_<indexname> Für jeden angelegten Index, d.h. für jede Instanz eines Indextyps, wird eine eigene Tabelle Z_<indexname> erzeugt, um darin - in index_create oder index_insert (vgl. 8.3 auf der nächsten Seite) berechnete - z-Codes zu speichern. Einträge der Tabelle bestehen aus z-Codes und Row-IDs als Referenz auf das zugehörige Objekt der indizierten Tabelle. R A B O C W D I D E O S B D O J E _ C G T E _ O G M E E O T M R E Y T R Y . . . ( . . . ) . . . I D A B Z C D E 1 C 2 3 O 2 D 1 E 0 Abb. 15: z-Code mit Referenz in der internen Tabelle z_<indexname> 36 8.3 Modul index zcode Dieses Modul enthält alle Funktionen und Prozeduren des z-Code-Primärfilters, welche die ODCI-Schnittstelle implementieren und direkt von PL/SQL aufgerufen werden. 8.3.1 index create OCINumber *index_create( OCIExtProcContext *ctx, char *metadata, char *indexname, char *tablename ) Die Funktion index_create füllt die interne z-Code-Tabelle, indem sie für alle Tupel der indizierten Tabelle z-Codes berechnet und diese zusammen mit der RowID als Referenz in die interne Tabelle speichert. Dadurch wird der z-Code-Index initialisiert. 8.3.2 index insert OCINumber *index_insert( OCIExtProcContext *ctx, char *metadata, char *indexname, char *tablename, char *rowid, sdo_geometry *geo, sdo_geometry_ind *geo_ind, OCIType *tdo ) Beim Einfügen von Datensätzen in die indizierte Tabelle, wird diese Funktion für jedes Tupel einmal aufgerufen. Die dabei übergebenen Paramter rowid und Geometriedaten geo werden dabei benutzt, um den dem Tupel zugehörigen z-Code zu berechnen und zusammen mit rowid in die interne Tabelle zu schreiben. 8.3.3 index delete void index_delete( OCIExtProcContext *ctx, char *ztable, char *rowid ) Zum Löschen eines Datensatzes aus dem Index werden alle z-Codes aus der internen Tabelle gelöscht, welche als Referenz die gleiche Row-ID wie die in rowid übergebene besitzen. 37 8.3.4 index start void index_start( OCIExtProcContext *ctx, ScanInfo *scinf, ScanInfo_ind *scinf_ind, char *pr, OCINumber *start_oci, OCINumber *stop_oci, sdo_geometry *cmpval, sdo_geometry_ind *cmpval_ind, OCIType *tdo, char *metadata, char *indexname, char *tablename ) Beim Ausführen einer SELECT-Anweisung wird von Oracle zuerst über ODCIIndexStart diese Prozedur aufgerufen, um die Anfrage für ODCIIndexFetch vorzubereiten. Sie reserviert über CreateScanContext Speicher, initialisiert die Struktur und speichert sie über einen Schlüssel in scinf ab. 8.3.5 index fetch void index_fetch( OCIExtProcContext *ctx, ScanInfo *self, ScanInfo_ind *self_ind,) OCINumber *nrows_oci, OCIArray **rids, short *rids_ind ) Die Prozedur index_fetch holt sich über self die von index_start vorbereitete ScanCtx-Struktur und beginnt, die Row-IDs der angeforderten Datensätze in rids zu speichern. Da höchstens nrows_oci viele Einträge in rids gespeichert werden dürfen, sicher index_fetch bei Erreichen dieser Anzahl seinen Zustand in self und gibt diesen beim Beenden an Oracle zurück. index_fetch wird danach über ODCIIndexFetch solange wieder aufgerufen, bis durch einen NULL-Eintrag im ridsArray signalisiert wird, daß alle Datensätze gefunden sind. 8.3.6 index close void index_close( OCIExtProcContext *ctx, ScanInfo *self, ScanInfo_ind *self_ind,) index_close gibt lediglich die ScanCtx-Struktur wieder frei. 8.3.7 test data Zur Anpassung des dem z-Code-Algorithmus zugrundeliegenden Kartenausschnitts bestimmt diese Funktion die maximalen und minimalen Koordinaten der zu indizierenden Tupel. test_data ist eine nicht öffentliche Funktion und wird nur beim Anlegen eines z-Code-Index von index_create aufgerufen. 38 I n d e x S e l e c t 1 O D C I I n i n d e d x e _ x s S t a t a . r t r t 2 O D C i n I I n d e d x e _ x f e F t c e t c O h D C i n d I I n e x d _ e c x l o r a c l e h 3 O . C s l o s . e e Abb. 16: Prinzipielle Arbeitsweise von select 8.4 Modul index metadata Die in diesem Modul zusammengefassten Fuktionen dienen dem Lesen und Schreiben von Daten in der Metadatentabelle. 8.4.1 indexinfo type Dieser Datentyp wird verwendet, um Metadaten aus der Metadatantabelle zu lesen und sie zwischen den C-Routinen auszutauschen. 8.4.2 index read metadata int index_read_metadata(DBConnectionHandle dbhp, indexinfo info) index_read_metadata liest das Tupel aus der Metadatentabelle, welches mit den Schlüsselwerten indexname und tablename übereinstimmt, in eine indexinfo_typeStruktur ein. 39 8.4.3 index write metadata int index_write_metadata(DBConnectionHandle dbhp, indexinfo info) Diese Funktion schreibt den Inhalt einer indexinfo_type-Struktur in die Metadatentabelle. 8.4.4 index delete metadata int index_delete_metadata(DBConnectionHandle dbhp, indexinfo info) index_delete_metadata löscht das durch die Schlüsselwerte indexname und tablename identifizierbare Tupel aus der Metadatentabelle. 8.5 Modul index scancontext Beim Ausführen von ODCIIndexStart wird das IndexFetch vorbereitet, indem ein Speicherbereich reserviert und initialisiert wird. In ODCIIndexFetch wird dieser Speicherbereich benutzt, um mit Oracle zu kommunizieren und so die angeforderten Datensätze zurückzugeben. In ODCIIndexClose wird der Speicher wieder freigegeben.Die Routinen dieses Moduls dienen dem Handling des eben beschriebenen Speicherbereichs. 8.5.1 CreateScanContext int CreateScanContext( DBConnectionHandle dbh, ScanInfo *indexObj, ScanInfo_ind *indexObj_ind,) ScanCtx **ctx, indexinfo info ) CreateScanContext reserviert Speicher für ctx. Der Zeiger auf diese Struktur wird dann mit OCI-Funktionen über einen Schlüssel codiert und in indexObj gespeichert. 8.5.2 FillScanContext int FillScanContext( ScanCtx *ctx, char *opname, int start,) int stop, indexinfo info, char *zcodestr, int mode ) Diese Funktion füllt die ScanCtx-Struktur mit den übergebenen Werten. Dies dient nur der Übersichtlichkeit des Programms. Die Variable mode legt den Zugriff in index_fetch fest. HINWEIS: Zur Zeit ist nur die einfache single query-Version implementiert. 40 8.5.3 GetScanContext int GetScanContext( DBConnectionHandle dbh, ScanInfo *indexObj, ScanCtx **ctx ) Mit Hilfe des übergebenen Zeigers indexObj wird über den enthaltenen Schlüssel die gespeicherte ScanCtx-Struktur ctx gelesen. 8.5.4 DisposeScanContext int DisposeScanContext( DBConnectionHandle dbh, ScanInfo *indexObj, ScanCtx *ctx ) Die Funktion DisposeScanContext gibt den für ctx reservierten Speicher wieder frei. 8.5.5 Datentyp ScanCtx Der Datentyp ScanCtx ist eine Struktur bestehend aus den zum Ausführen von ODCIIndexFetch benötigten Daten. 8.6 Modul DBUtils Leider können aus dem von PL/SQL nach C übergebenen OCIExtProcContextHandle nur einmal erfolgreich mittels OCIExtProcGetEnv die übrigen Handles wie User-Handle, Environment-Handle oder Error-Handle gewonnen werden. Beim zweiten Aufruf von OCIExtProcGetEnv liefert diese Funktion ohne Fehlermeldung nur unsinnige Zeiger zurück. Daher ist es nötig, die einmal gewonnenen Handles zu speichern und innerhalb des eigenen Programms weiterzureichen. 8.6.1 connectByCtx int connectByCtx (OCIExtProcContext *ctx, DBConnectionHandle *dbhp) Diese Funktion reserviert Speicher für eine DBConnectionHandle-Struktur und sichert die mittels OCIExtProcGetEnv gewonnenen Handles in ihr. 8.6.2 oraCheckErr int oraCheckErr ( DBConnectionHandle dbh, char *errorstr, int retVal ) Zum Abfragen von Fehlern verwendet oraCheckErr die Handles in der DBConnectionHandle-Struktur. errorstr ist ein benutzerdefinierter Text, der zusätzlich zum Fehlertext mitausgegeben wird. retVal ist der Errorcode, der von OCI-Funktionen zurückgegeben wird. 41 8.6.3 Datentyp DBConnectionHandle In dieser Struktur sind alle wichtigen Handles und weitere Variablen zur Fehlerbehandlung enthalten. 8.7 Modul geom zcode In diesem Modul werden nur die Funktionen zcode_calc und zcode_createquery genutzt. Alle anderen Funktionen werden nicht mehr genutzt, da sie z.B. statische Variablen nutzen. 8.7.1 zcode calc zcode_calc( point_type *max, point_type *min, char **zcode_return, int zcode_maxx, int zcode_maxy, int zcode_minx, int zcode_miny, int zcode_maxdepth ) Die Funktion berechnet den z-Code für das duch max und min gegebene Rechteck und gibt diesen in zcode_return zurück. Die letzten fünf Parameter enthalten dazu die Informationen über das zugrundeliegende Kartenfenster und die maximale zCodelänge. 8.7.2 zcode createquery char *zcode_createquery(char *zcodestr) Der Where-Teil der Anfrage in index_fetch für den single_query-Modus wird durch diese Funktion berechnet. 8.8 Modul geom op Dieses Modul enthält Funktionen, um aus den indizierten Daten vom Typ SDO_GEOMETRY die geometrischen Informationen zu lesen und sie in ein zur Weiterverarbeitung geeignetes Format zu konvertieren. Alle Datentypen und Funktionen dieses Moduls existieren in einer einfachen und eines _ws-Version, welche zusätzlich die Anzahl der Koordinatenpunkte abspeichert.Alle hier dokumentierten Funktionen nutzen nur OCI-Aufrufe zur Speicherverwaltung. 42 8.8.1 GetVArray( ws) GetVArray( OCIEnv *envhp, OCIError *errhp, sdo_geometry *geo, int *PointCnt, point_type **PointArrayReturn ) Enthält SDO_GEOMETRY einen andere Struktur als SDO_POINT, so sind die Koordinaten (abwechselnd x und y) im SDO_ordinate-Array gespeichert. Diese werden nach der Konvertierung in Integer nacheinander paarweise in point gespeichert. 8.8.2 GetVArray info( ws) GetVArray_info( DBConnectionHandle dbhp, sdo_geometry *geo, int *PointCnt, point_type **PointArrayReturn ) Diese Funktion extrahiert die Informationen des SDO_elem_info-Arrays und speichert die Werte in die x-Koordinaten des übergebenen PointArrayReturn. 8.8.3 SetVArray( ws) SetVArray( DBConnectionHandle dbhp, sdo_geometry *geo, int *PointCnt, point_type *PointArray ) SetVArray füllt das SDO_ordinate-Array der Struktur geo mit den Koordinaten aus PointArray. 8.8.4 SetVArray info( ws) SetVArray_info( DBConnectionHandle dbhp, sdo_geometry *geo, int *PointCnt, point_type *PointArray ) SetVArray_info füllt das SDO_elem_info-Array der Struktur geo mit den x-Werten des Arrays PointArray. 8.8.5 GetSDO Point( ws) GetSDO_Point( OCIError *errhp, sdo_geometry *geo, int *PointCnt, point_type **point ) Falls geo eine Struktur vom Typ SDO_POINT enthält, sind die zugehörigen Koordinaten in einem separatem Tripel vom Typ sdo_point_type gespeichert. Diese werden gelesen, in das Integer-Format konvertiert und die x- und y-Koordinate werden in PointArrayReturn gespeichert. 43 8.8.6 GetRectangle( ws) GetRectangle( point_type_ws *PointArray_ws, point_type sdo_geometry **prightlower, point_type **pleftupper ) Bestimmt das umgebende Rechteck der in PointArray_ws übergebenen Struktur und gibt dieses über die zwei Eckpunkte prightlower und pleftupper zurück. 8.8.7 Datentyp point type( ws) Der Datentyp point_type ist ein Array von Tupeln zur Speicherung von zweidimensionalen Koordianten. point_type_ws besitzt zusätzlich einen Eintrag der angibt, wieviele Punkte in der Struktur gespeichert sind. 8.9 Modul geom data In diesem Modul sind alle Datentypen, die zum Austausch von Daten vom Typ SDO_GEOMETRY mit Oracle benötigt werden. 8.10 Modul geom err 8.10.1 checkerr void checkerr(OCIError *errhp, sword status) void checkerr2( OCIExtProcContext *ctx, OCIError *errhp, char *myerr, sword status ) Diese Funktionen dienen der Fehlerbehandlung beim Aufruf von OCI-Funktionen. Sie werden nur noch in den Modulen geom_op und geom_zcode verwendet. 8.10.2 printerror ora void printerror_ora( OCIExtProcContext *ctx, int errnum, char *errmsg ) printerror_ora erzeugt eine Exception mit der Nummer errnum und dem Fehlertext errmsg. Wird als Fehlernummer 29400 übergeben, wird die Exception als benutzerdefinierter data-cartridge-error bis nach PL/SQL zurückgegeben. Dies kann z.B. zum Debugging genutzt werden. 44 9 Exakte geometrische Operationen 9.1 PL/SQL 9.1.1 MyIntersects.sql In dieser Datei wird die Anmeldung des Intersects-Operators vorbereitet, um diesen auch bei nicht-indexunterstützten Anfragen benutzen zu können (siehe Kapitel 12.2 auf Seite 53). 9.2 C-Modul RSTOps In diesem Modul ist der Intersects-Operator implementiert. Er basiert auf dem in Kapitel 3.3.1 auf Seite 20 beschriebenen Planesweep-Algorithmus. Dieser wird im Fall des R*-Baumes über die Hilfsfunktion callOps aufgrufen. 9.2.1 callOps OCITable *callOps( OCIExtProcContext *ctx, OCITable *rids, char *tablename, char *opname, sdo_geometry *cmpval ) Die Funktion wird während des ODCIIndexFetch aus PL/SQL mit einem NestedTable von Row-Ids der Kandidaten des Primärfilters des R*-Baumes aufgerufen. Da aufgrund des Clippings Row-Ids mehrfach vorkommen können, werden diese hier entfernt. Mit Hilfe einer Select-Anfrage werden die Geometrieobjekte zu den Row-Ids aus der indexierten Tabelle tablename gelesen. Der zu opname adäquate Operator (in diesem Falls nur Intersects) wird mit dem aktuell gelesenden Geometrieobjekt und dem Vergleichsobjekt cmpval aufgerufen. Gemäß des Rückgabewertes des Operators wird die Row-Id zu dem geprüfeten Objekt gelöscht oder verbleibt in der Collection. Nachdem alle Kandidaten überprüft worden sind, wird die Collection mit dem nun exakten Ergebniss an Oracle zurückgeliefert. Zur Eleminierung der mehrfachen Row-Ids in der Collection wird diese zuerst mit Hilfe eine Heap-Sorts sortiert. Dabei werden folgende UnterRoutinen benutzt: • void siftDown( int i, int m ) Lässt das i-te Element von bis zu Stelle t heruntersickern. • char *getElem( int i, char *ridstr ) Schreibt den Inhalt des Elements, das an der i-ten Stelle der Collection steht, nach ridstr. • void assignElem( int i, char *ridstr ) Weist dem i-ten Element der Collection die Row-Id ridstr zu. • void exchangeElem( int i, int j ) Vertauscht die Einträge i und j der Collection. 45 9.2.2 intersectsRetNum OCINumber *intersectsRetNum( OCIExtProcContext *ctx, sdo_geometry *geo1, sdo_geometry_ind *geo_ind1, OCIType *geom_tdo1, sdo_geometry *geo2, sdo_geometry_ind *geo_ind2, OCIType *geom_tdo2 ) Um einen Operator in Oracle anzumelden, der die Funktion intersects benutzt, wird diese Funktion benötigt. Es wird Speicher für die Datenbankhandles allokiert, diese aus dem Service-Context gewonnen und in der Struktur dbhp gespeichert. Die Funktion intersects wird mit den Geometrieobjekten geo1 und geo2 aufgerufen. Der Rückgabewert wird in eine OCINumber umgewandelt und zurück nach PL/SQL gegeben. 9.2.3 intersectsRetInt int intersectsRetInt( OCIExtProcContext *ctx, OCIEnv *envhp, OCISvcCtx *svchp, OCIError *errhp, sdo_geometry *geo1, sdo_geometry *geo2 ) Damit auch andere C-Routinen die Funktion intersect benutzen können, werden die dort benötigten Datenbankhandels in die Struktur dbhp von Typ dbhandles geschrieben und dann zusammen mit den beiden Geometrieobjekten geo1 und geo2 übergeben. 9.2.4 intersects int intersects( dbhandles dbhp, sdo_geometry *geo1, sdo_geometry *geo2 ) Zur Vorbereitung des Planesweep wird die Q-Liste mit den Haltepunkten der Geometrieobjekte gefüllt. Dies geschieht durch den Aufruf der Prozedur getIntoQList. Vorher wird Speicher für eine Struktur allokiert, der die Zeiger auf die jeweiligen Listen enthält. Es wird die die Funktion planesweep aufgerufen, die den Sweep durch die Ebene (wie in Kapitel 3.3.1 auf Seite 20 beschrieben) durchführt und den Ergebniswert zurückliefert. Das Ergebnis sagt lediglich aus, daß sich die beiden Geometrieobjekte den Liniensegmenten nach schneiden. Es wird nicht, wie in Kapitel 3.3.3 auf Seite 21 beschrieben, überprüft, ob ein Objekt in dem anderen gänzlich enthalten ist. 9.2.5 getIntoQList void getIntoQList( dbhandles dbhp, 46 plnswpdata *plnswp, sdo_geometry *geo, int n ) Für ein Geometrieobjekt mit der Nummer n werden Koordinaten aus den SDO_ORDI NATES-Array gelesen und die Haltepunkte zusammen mit einem Zeiger auf das zugehörige Liniensegment die Q-Liste geschrieben. Dieses geschieht mit Hilfe der Prozedur insertLineIntoQ. 9.2.6 insertLineIntoQ void insertLineIntoQ( dbhandles dbhp, plnswpdata *plnswp, int_point_type start, int_point_type end, int n ) Ein Liniensegment mit dem Startpunkt start und dem Endpunkt end, das zu dem Objekt mit der Nummer n gehört wird in die Q-Liste einsortiert. start und end bilden dabei zwei Haltepunkte, die als Objekt vom Typ qnode in die Q-Liste eingetragen werden. Dabei werden bei gleichwertigen Haltepunkten zweier Liniensegmenten die Startpunkte vor den Endpunkten einsortiert. Jedem qnode wird ein Element von Typ lnode zugeordnet, die die Informationen zu den Liniensegmenten enthalten, die benötigt werden, um den Schnitt zweier Liniensegmente zu prüfen. 9.2.7 planeSweep int planeSweep( plnswpdata *plnswp ) Hier wird der in Planesweep-Alghrithmus ausgefuehrt. Die Funktion bekommt in der Struktur plnswp die Zeiger auf die Q- und L-Listen übergeben, die zur Durchführung benötigt werden. 9.2.8 insertIntoLList void insertIntoLList( plnswpdata *plnswp ) Trift die Sweepline auf einen Haltepunkt in der Q-Liste, der ein Startpunkt eines Liniensegments ist, wird dieses Liniensegment mit schon vorhanden Liniensegmenten in der L-Liste auf Schnitt überprüft. Liegt kein Schnitt vor, so wird dieses Liniensegment ebenfalls in die L-Liste eingetragen. 9.2.9 deleteFromLList void deleteFromLList( plnswpdata *plnswp ) Trifft die Sweepline auf einen Haltpunkt in der Q-Liste, der ein Endpunkt eines Liniensegmentes ist, so wird das zugehörige Liniensegment aus der L-Liste entfernt. 47 9.2.10 checkIntersect int checkIntersect( lnode *lnode1, lnode *lnode2 ) Es wird überprüft, ob sich die übergebenen Liniensegmente, die in lnode1 und lnode2 beschrieben stehen, schneiden. Dem entsprechend wird eine 1 oder eine 0 zurückgeliefert. 9.2.11 getStatus int getStatus( lnode *lnode ) Um festzustellen, wie ein Linensegment in der Ebene liegt, wird diese Funktion aufgerufen. Liegt das Segment waagerecht in der Ebene, so wird eine 1 zurückgeliefert. Liegt es senkrecht in der Ebene, wird eine 2, und handelt es sich um einen Punkt, so wird eine 3 zurückgegeben. Handelt es sich um ein Liniensegment der Steigung größer gleich 0 und ungleich unendlich, wird eine 0 zurückgegeben. 9.2.12 freeExtProcMem void freeExtProcMem( dbhandles dbhp, plnswpdata *plnswp ) Speicher der für die Q- und L-Listen mit OCIExtProcAllocCallMemory allokiert wurde, wird wieder freigegeben. 48 Teil IV Benutzung 10 R*-Baum mit Clipping 10.1 Installation • Entpacken Sie das Archiv RSTree.zip in ein beliebiges Verzeichnis. Dort müßten sich nun folgende Dateien befinden: MyIntersects.sql, RSTConf.sql, RSTIndex.sql, RSTOps.sql, RSTPath.sql, RSTPrep.sql, RSTRectangle.sql, RSTRemAll.sql, RSTRemIndextype.sql, RSTSupp.sql, RSTTreetable.sql, RSTClip.c, RSTOps.c, RSTSupp.c, RSTClip.h, RSTOps.h, RSTSupp.h,ocicompile2 und Makefile. • Geben Sie make RSTClip und make RSTOps ein, um die Shared Libraries RSTClip.so und RSTOps.so zu erstellen. Achten Sie darauf, daß die Betriebssystemumgebung beim Kompilieren die gleiche ist, wie die des DatenbankServers. • Öffnen Sie die Datei RSTConf.sql, passen Sie den Pfad der beiden Bibliotheksdateien an und schließen Sie die Datei wieder. • Starten Sie SQLPlus. • Rufen Sie die Datei mit RSTPrep.sql mit @RSTPrep auf. Alle benötigten Dateien werden nun geladen. • Es ist nun möglich einen Indextyp zu erzeugen, um einen Index anzulegen. 10.2 Anlegen eines Indexes • Geben Sie @RSTIndex ein, um einen neuen Indextyp für den R*-Baum anzulegen. Sie können mehere Indextypen mit unterschiedlichen Parametern anlegen. • Geben Sie den Namen des zu erstellenden Indextypes ein. • Geben Sie den Gütewert für die Approximation (max_error) ein, bei dem der Clippingalgorithmus noch aufgerufen werden soll. Ist dieser 0 so werden die Liniensegmente stark geclippt, um eine möglichst genaue Approximation zu erhalten. Soll gar kein Clipping vorgnommen werden, so geben Sie eine 1 ein. Der Wert sollte erfahrungsgemäß oberhalb von 0.7 liegen. • Geben Sie die Fläche ein, die ein Clippingobjekt minimal haben soll (min_area). Die Größe ist von dem verwendeten Maßstab der Geometriedaten abhängig. • Geben Sie die maximale Knotengröße M des R*-Baums ein. • Geben Sie die minimale Knotengröße m des R*-Baums ein. • Geben Sie den Parameter p des R*-Baums ein. 49 • Es wird nun der neue Indextyp für den R*-Baum angelegt. HINWEIS: Alle Tabellen- oder Indexnamen, die für einen R*-Baum-Index benutzt werden sollen, dürfen eine maximale Länge von 26 Zeichen nicht überschreiten. 10.3 Anfragen stellen Anfragen mit dem R*-Baum-Index stellen Sie folgendermaßen: SELECT <spalten> FROM <tabelle> WHERE intersects(objectgeometry, <Objekt vom Typ MDSYS.SDO_GEOMETRY>)=1; Beispiel: SELECT * FROM object_geometries WHERE intersects(objectgeometry, MDSYS.SDO_GEOMETRY(2,NULL,NULL, MDSYS.SDO_ELEM_INFO_ARRAY(1,2,1), MDSYS.SDO_ORD INATE_ARRAY(0,0,1000,1000)))=1; 10.4 Deinstallation 10.4.1 Löschen eines R*-Baum-Indextyps Es wurde eine Skriptdatei angelegt, die es ermöglicht einen R*-Baum-Indextyp ohne Kenntnis der internen Abhängigkeiten zu löschen. • Starten Sie SQLPlus und laden Sie mit @RSTRemoveIndextype die Datei RSTRemoveIndextype.sql. • Geben Sie bei der Aufforderung den Name des R*-Baum-Indextyps an, den Sie löschen möchten. Beachten Sie, daß wenn es sich bei diesem Indextyp nicht um einen R*-Baum-Indextyp handeln sollte, daß dieser unbrauchbar werden könnte. 10.4.2 Deinstallation des R*-Baum-Indexes Es wurde eine Skriptdatei angelegt, die es ermöglicht die gesamte R*-Baum-Index Implementation ohne Kenntnisse der internen Abhängigkeiten aus dem OracleDBMS zu löschen. • Starten Sie SQLPlus und laden Sie mit @RSTRemoveAll die Datei RSTRemoveAll.sql. Die R*-Baum-Index Implementation wird nun aus dem System entfernt. 50 11 z-Codes 11.1 Installation Zur Benutzung des z-Code-Index sind vier Dateien nötig: • zcodelib zcodelib ist die auf dem Oracle-Server ausführbare shared-library, welche die z-Code-Funktionen bereitstellt. • zcode_index.sql Diese Datei enthält die notwendigen Anweisungen, um einen Indextyp anzulegen, welcher auf die z-Code-Mechanismen zurückgreift. • zcodelib.sql Die Datei zcodelib.sql enthält lediglich den Befehl zur Deklaration der shared-library. Aufgerufen wird sie von zcode_index.sql. • zcode_ops.sql In dieser Datei befinden sich die Deklarationen der Index-Operatoren. Um einen z-Code-Index benutzten zu können, muß gegebenenfalls die Datei zcodelib durch Compilieren neu erstellt werden. Dabei ist zu beachten, daß die sharedlibrary auf dem Oracle-Sever lauffähig sein muß, d.h. daß der Compiler mit derselben Betriebsystemumgebung bzw. Hardwarearchitektur gestartet wird, mit der auch der zu verwendende Oracle-Server arbeitet. In der Datei zcodelib.sql muß weiterhin der Pfad der shared-library angepaßt werden. Danach kann durch Starten von zcode_index.sql ein z-Code Indextyp angelegt werden. 11.2 Anlegen eines z-Code-Index Mit dem Befehl CREATE INDEX <indexname> ON <table>(object_geometry) PARAMETERS (<zcodelen>, <maxx>, <maxy>, <minx>, <miny>) INDEXTYPE IS <indextypename> wird ein Index mit Benutzerparametern angelegt. Dabei gibt zcodelen die maximale Länge der zu berechnenden z-Codes an. Durch die Parameter maxx, maxy, minx, miny wird das dem z-Code-Algorithmus zugrundeliegende Kartenfenster festgelegt.Dieses Fenster kann später nur durch Löschen und Neuanlegen des Index verändert werden. Wird für alle vier Parameter der Wert 0 übergeben, werden die in der zu indizierenden Spalte vorhandenen Daten analysiert, um das Fenster automatisch zu bestimmen (z.Zt. nicht möglich).Wichtig ist, daß der Index nur auf einer Spalte vom Typ SDO_GEOMETRY angelegt werden kann. Ebenso muß darauf geachtet werden, daß das Kartenfenster groß genug gewählt wird, um später einzufügende Daten umschließen zu können. 51 Mit dem Befehl DROP INDEX <indexname> kann der Index wieder gelöscht werden. Die Befehle INSERT INTO <tablename>, UPDATE <tablename>, DELETE FROM <tablename> und TRUNCATE <tablename> können wie gewohnt verwendet werden. HINWEIS: Alle Tabellen- oder Indexnamen, die für einen z-Code-Index benutzt werden sollen, dürfen eine maximale Länge von 26 Zeichen nicht überschreiten. 11.3 Anfragen Zur Unterstützung von Anfragen stellt der z-Code-Index zur Zeit zwei Operatoren bereit: • dummy(a,b) Der dummy-Operator umgeht den Sekundärfilter und liefert direkt die Kandidatenmenge des Primärfilters. • intersects(a,b) Der intersects-Operator liefert die Menge, bestehend aus allen b tatsächlich exakt schneidenden Datensätzen in Spalte a. Der Syntax der Anfragen sieht wie folgt aus: SELECT {<rows>} FROM <table> WHERE dummy(object_geometry, {SDO_GEOMETRY})=1 SELECT {<rows>} FROM <table> WHERE intersects(object_geometry, {SDO_GEOMETRY})=1 52 12 Intersects-Operator 12.1 Anlegen des Intersects-Operators Es ist möglich den Intersects-Operator zu nutzen, ohne einen R*-Baum-Index anzulegen. Dazu wurde eine Datei vorbereitet, die die Anmeldung des Operators unter den Namen myintersects vornimmt. • Passen Sie, wenn noch nicht geschehen, die Pfade der Shared Libraries in der der Konfigurationsdatei RSTConf.sql an und kompilieren Sie die Shared Libaries (siehe Kapitel 10.2 auf Seite 49). • Möchten Sie den Intersects-Operator anders benennen, so ändern Sie den Namen des Operators in der Datei MyIntersects.sql in der Zeile 23 entsprechend ab. • Starten Sie SQLPlus und laden Sie mit @MyIntersects die Datei MyInter sects.sql. Es wird nun der Intersects-Operator mit entsprechenden Namen angemeldet. HINWEIS: Alle Tabellen- oder Indexnamen, die für einen R*-Baum-Index benutzt werden sollen, dürfen eine maximale Länge von 26 Zeichen nicht überschreiten. 12.2 Anfragen stellen mit dem Intersects-Operator Anfragen mit dem Intersects-Operator stellen Sie folgendermaßen: SELECT <spalten> FROM <tabelle> WHERE intersects(objectgeometry, <Objekt vom Typ MDSYS.SDO_GEOMETRY>)=1; Beispiel: SELECT * FROM object_geometries WHERE intersects(objectgeometry, MDSYS.SDO_GEOMETRY(2,NULL,NULL, MDSYS.SDO_ELEM_INFO_ARRAY(1,2,1), MDSYS.SDO_ORD INATE_ARRAY(0,0,1000,1000)))=1; 53 Teil V Tests 13 R*-Baum Zu Testzwecken wurden 4 Indexe angelegt. Da das Clipping-Modul nur Streckenzüge clippt, wurden Objekte vom Typ SDO_POINT und SDO_POLYGON aus den Testtabellen entfernt. Es wurde zwei Tabellen mit 7556 bzw. 3770 Linienzügen indexiert. Auf beiden Tabellen wurde jeweils ein Index mit und ein Index ohne Clipping angelegt. Die Parameter des R*-Baumes blieben dabei für alle Indexe gleich: Index Index1 Index2 Index3 Index4 Objekte Tabelle 7556 7556 3700 3700 max error min area M m p 1 0.7 1 0.7 egal 1000000 egal 1000000 10 10 10 10 4 4 4 4 3 3 3 3 Objekte im R*-Baum 7556 36451 3770 18424 13.1 Zeitbedarf für das Anlegen eines R*-Baum-Indexes Beim Anlegen eines R*-Baum-Indexes wird zuerst das Clipping-Modul aufgerufen, das die Geometrieobjekte liest, eventuell clippt, die Approximationsrechtecke berechnet und zusammen mit den Row-Ids der Originalobjekte in eine temporäre Tabelle schreibt. Dann werden diese vom R*-Baum aus der Tabelle gelesen und in die Baumstruktur eingefügt. • Zeitbedarf für das Clipping der Linienzüge und das Schreiben der Approximationsrechtecke in die temporäre Tabelle Index Index1 Index2 Index3 Index4 gelesene. Objekte 7556 7556 3770 3770 geschriebene Objekte 7556 36451 3770 18424 Zeitbedarf in Sekunden 4.62 14.49 2.33 7.04 Ø Operationen pro Sekunde 3271 3037 3236 3143 An diesen Meßdaten kann man gut erkennen, daß der Clipping-Algorithmus eine lineares Zeitverhalten hat. • Zeitbedarf für das Lesen der Rechtecke aus der temporären Tabelle Index Index1 Index2 Index3 Index4 gelesene Objekte 7556 36451 3770 18424 Zeitbedarf in Sekunden 0.36 1.65 0.19 0.95 54 Ø Operationen pro Sekunde 20988 22091 19842 19393 Für das Lesen der Rechtecke wurde ein Cursor unter Benutung von Native Dynamic SQL angelegt, welcher die Tabelle natürlich in O(n) Schritten durchläuft. • Zeitbedarf für das Einfügen der Rechtecke in den R*-Baum Index Index1 Index2 Index3 Index4 Anzahl Objekte 7556 36451 3770 18424 Zeitbedarf in Minuten 07:48 43:41 03:40 20:55 Ø Operationen pro Sekunde 16.14 13.91 17.14 14.68 Der Zeitbedarf für das Anlegen des Indexes wird klar vom Einfüge-Algorithmus des R*-Baumes dominiert. Man erkennt hier sehr schön das Laufzeitverhalten von O(n log n) Obwohl der Zeitbedarf für Anlegen eines Indexes nicht so wichtig ist, wie der Zeitbedarf fürs Selektieren, könnte eine Implementation in C sicherlich einen großen Geschwindikeitszuwachs erzielen. 13.2 Zeitbedarf für das Selektieren von Daten mit Hilfe des Intersects-Operators Beim Selektieren von Daten wird zunächst die Kandidatenmenge vom R*-Baum bestimmt, die dann dem Sekundärfilter übergeben wird, der die zeitintensiven Prüfungen auf den Geometriedaten vornimmt. Folgende Anfrage wurde benutzt: SELECT rowid,objectno,objectpartno FROM <Testtabelle> WHERE intersects (objectgeometry,MDSYS.SDO_GEOMETRY(2,NULL,NULL,MDSYS.SDO_ELEM_INFO_ ARRAY(1,2,1),MDSYS.SDO_ORDINATE_ARRAY(354800000,580800000,354900000, 580900000)))=1; • Zeitbedarf für die Bestimmung der Kandidatenmenge (nur Primärfilter) Index Index1 Index2 Index3 Index4 Objekte insgesamt 7556 36451 3770 18424 Kandidaten (inkl. Clipping) 55 310 32 203 Kandidaten (unique) 55 54 32 32 Zeitbedarf in Sekunden 0.71 2.72 0.61 1.57 Nur im Fall des Index2 ist die Kandidatenmege (unique) um ein Objekt kleiner als ohne Clipping. Auffalled ist, daß die Kandidatenmenge inklusive der Clipping-Objekte trotz besserer Approximation nicht kleiner wird. Im Gegenteil, es müssen mehrfach vorkommene Row-Ids erst wieder herausgefiltert werden. Eine Erklärung dafür könnte sein, das zwar die Daten besser approximiert werden, das Vergleichsobjekt für den R*-Baum jedoch nicht geclippt wird. Somit schneidet, bzw. überdeckt es gegebenenfalls mehrere Clipping-Objekte eines Geometrieobjektes. Deswegen wird später eine Gegenprobe mit einem horizontal in der Ebene liegenden Streckenzug als Vergleichswert gemacht. 55 • Zeitbedarf für komplette Intersects-Anfrage (Primär- + Sekundärfilter) Index Index1 Index2 Index3 Index4 Objekte insgesamt 7556 36451 3770 18424 Kandidaten (unique) 55 54 32 32 exakte Objekte 8 8 6 6 Zeitbedarf in Sekunden 0.80 2.78 0.39 1.61 Wie man sieht, benötigt der Intersects-Operator für die Vergleiche nur sehr wenig Zeit. Teilweise scheint die Anfrage mit Intersects-Operator sogar schneller zu sein, als ohne. Dieses ist wohl auf Messungenauigkeiten zurückzuführen, da leider nur eine relativ kleine Testdatenmenge benutzt werden konnte. Enttäuschend jedoch ist, daß die Approximation keinerlei Geschwindigkeitsvorteile gebracht hat, sie bremst sogar die Anfrage aus. Der Mehraufwand, den der R*-Baum zu bewältigen hat, kostet während der Anfragebearbeitung zu viel Zeit. • Intersects-Anfrage mit 100%ig genau approximierten Vergleichsobjekt Um zu testen, ob ein Vergleichsobjekt, das für den Primärfilter 100%ig genau durch ein Rechteck approximiert werden kann, eine kleinere Kandidatenmenge liefert, wurde folgende Anfrage gestellt: SELECT rowid,objectno,objectpartno FROM <Testtabelle> WHERE intersects(objectgeometry,MDSYS.SDO_GEOMETRY(2,NULL,NULL,MDSYS. SDO_ELEM_INFO_ARRAY(1,2,1),MDSYS.SDO_ORDINATE_ARRAY(355000000, 580800000,356000000,580800000)))=1; Index Index1 Index2 Objekte insgesamt 7556 36451 Kandidaten (unique) 31 31 exakte Objekte 29 29 Zeitbedarf in Sekunden 0.63 0.93 Die Kandidatenmenge des Index mit Clipping stimmt mit der Kandidatenmenge des Indexes ohne Clipping überein, sodaß man annehmen kann, daß die Approximation des Vergleichobjektes in den vorigen Tests zu ungenau war. Daß das Vergleichsobjekt sehr gut approximiert wurde, läßt sich auch daran erkennen, daß die Menge der Kadidaten größenordnungsmäßig nahe an der des exakten Ergebnisses liegt. Aber auch hier ist die Kandidatenmege nicht kleiner als beim Index, der ohne Clipping-Objekte arbeitet. Gut zu erkennen ist, daß die Suche durch den Baum mit ohne Clipping-Objekte ca. ein Drittel schneller war, weil weniger Objekte im R*-Baum gespeichert sind. 56 14 z-Codes 14.1 Zeitbedarf fürs Anlegen eines z-Codes Indexes Zu Testzwecken wurden auf vier verschiedenen Tabellen z-Code-Indexe, mit einer maximaler z-Code-Länge von 10, angelegt. Tabelle Tabelle1 Tabelle2 Tabelle3 Tabelle4 Tupelanzahl 11184 22368 33552 67104 mittlere Zeit 7,4s 14,3s 21,4s 43,0s Ø Tupel/Sekunde 1507 1564 1566 1559 Man kann erkennen, daß die Komplexität des Einfügealgorithmus im Bereich von O(n) liegt, da die durchschnittliche Bearbeitungsgeschwindigkeit auch bei größeren Datenmengen relativ konstant bleibt. Dies ist plausibel, da alle Tupel beim Einfügen genau einmal gelesen werden und dann direkt der zugehörige z-Code berechnet und gespeichert wird. 14.2 Zeitbedarf fürs Ausführen einer Anfrage Um die Zugriffsgeschwindigkeiten zu messen, wurden zwei Tabellen und jeweils drei unterschiedliche Anfragen verwendet. 14.2.1 Primärfilter Tabelle Tabelle1 Tabelle1 Tabelle1 Tabelle2 Tabelle2 Tabelle2 Anfrage Anfrage1 Anfrage2 Anfrage3 Anfrage1 Anfrage2 Anfrage3 Tupelanzahl 11184 11184 11184 22368 22368 22368 Kandidatenmenge 484 533 1126 968 1066 2252 mittlere Zeit 0.58s 0.58s 0.61s 0.62s 0.65s 0.74s Man kann vermuten, daß die Verarbeitungszeit mit wachsender Anzahl von Tupeln und sehr großen Kandidatenmengen steigt. Dies ist vereinbar mit der Tatsache, daß die infragekommenden z-Codes in konstanter Zeit berechnet werden können. Dann müssen allerdings alle rowids, die das Präfix-Kriterium erfüllen gelesen und an Oracle übergeben werden. Dies dauert bei einer größeren Kandidatenmenge natürlich länger. Weiter wird auch das Auffinden der entsprechenden Tupel mit zunehmender Gesamttupelzahl aufwendiger. 57 14.2.2 Intersects Tabelle Tabelle1 Tabelle1 Tabelle1 Tabelle2 Tabelle2 Tabelle2 Anfrage Anfrage1 Anfrage2 Anfrage3 Anfrage1 Anfrage2 Anfrage3 Tupelanzahl 11184 11184 11184 22368 22368 22368 Kand. 484 533 1126 968 1066 2252 Ergebnismenge 0 6 1 0 12 2 mittlere Zeit 1.60s 1.71s 3.13s 2,61s 2,84s 5,44s Es läßt sich deutlich erkennen, wie die Verarbeitungszeit bei unterschiedlichen Anfragen mit der Größe der Kandidatenmenge wächst. Dies hängt damit zusammen, daß für jeden Kandidaten der exakte geometrische Intersects-Operator aufgerufen werden muß. Subtrahiert man die Zeiten des Primärfilters von den Gesamtzeiten der Intersects-Anfrage, erhält man folgende Werte: Tabelle Tabelle1 Tabelle1 Tabelle1 Tabelle2 Tabelle2 Tabelle2 Anfrage Anfrage1 Anfrage2 Anfrage3 Anfrage1 Anfrage2 Anfrage3 Tupelanzahl 11184 11184 11184 22368 22368 22368 Kand. 484 533 1126 968 1066 2252 mittlere Zeit 1.02s 1.13s 2.52s 1,99s 2,19s 4,70s Ø Kand./Sek. 475 472 447 486 487 479 Im Rahmen der Messungenauigkeiten und der unterschiedlichen Beschaffenheit der selektierten Geometrien kann man vermuten, daß die Geschwindigkeit des exakten geometrischen Intersects-Operator als konstant angenommen werden kann. Somit hängt die Gesamtverarbeitungsgeschwindigkeit fast ausschließlich mit der Größe der Kandidatenmenge zusammen. 58 Teil VI Fazit und Anmerkungen 15 R*-Baum Als erstes ist festzuhalten, daß die Erwartungen die an das Clipping-Modul gebunden waren, nicht erfüllt wurden. Größtes Problem scheint zu sein, daß durch die zusätzlichen Daten, die durch die Clipping-Objekte anfallen, der R*-Baum zu stark ausgebremst wird. Weiterhin wird das Vergleichsobjekt, welches dem Operator übergeben wird, nicht geclippt. Dessen Approximationsrechteck scheint in den Tests mehrere ClippingObjekte zu überlappen, so daß diese als Kandidaten zurückgeliefert werden. Würde man das Vergleichsobjekt ebenfalls clippen, so müßte der R*-Baum entsprechend der Anzahl der geclippten Objekte öfter durchsucht werden. Dieses würde wiederum zu einer starken Performance-Einbuße führen. Durch die Tests, aber auch schon während der Implementationsphase hat sich gezeigt, daß es ein großer Vorteil wäre, wenn der R*-Baum auch C implementiert wäre. Zum Einen würden die Prozeduren für das Einfügen und das Heraussuchen der Daten dadurch wohl wesentlich schneller, zum Zweiten würde auch der CallOverhead für den Aufruf des Clipping-Moduls und des Operators vermieden werden. Wahrscheinlich sind durchaus bessere Ergebnisse als die in den Tests erzielbar. Dafür ist es aber notwendig die Parameter des Clipping-Moduls genau auf die gegebenen Geometriedaten anzupassen. Dieses war leider aufgrund der langen Zeit, die der Index fürs das Anlegen benötigt, nicht möglich. 16 z-Codes Aufgrund der einfachen Implementation mit nur einem z-Code pro Objekt geht das Anlegen eines z-Code Index sehr schnell. Aber auch mit einer anderen Art der zCode-Bildung wird das Anlegen eines z-Code-index aufgrund einer Komplexität von O(n) sicher nicht sehr langsam werden. Die Bearbeitungsdauer einer Select-Anfrage hängt direkt von der Größe der Kandidatenmenge ab, welche der Primärfilter liefert, da die Algorithmen zur Berechnung der infragekommenden z-Codes mit Datenbankzugriffen im nahezu konstanten Bereich arbeiten. Somit ließen sich wahrscheinlich bessere Ergebnisse erzielen, wenn ein komplexerer Ansatz (vgl. 2.2 auf Seite 16) umgesetzt würde. Eine andere Alternative wäre, den Clipping-Algorithmus (vgl. 1.4.4 auf Seite 12) der z-Code-Berechnung vorzuschalten. 59 Literatur [BK00] H. H. Brüggemann, C. Kleiner. Data Cartridges für Oracle 8i am Beispiel räumlicher Daten. Vorlesungsskript, Institut für Informatik, Universität Hannover, 2000. [BKSS90] N. Beckmann, H.-P. Kriegel, R. Schneider, B. Seeger: The R∗ -Tree: An Efficient and Robust Access Method for Points and Rectangles. In H. GarciaMolina, H. V. Jagadish (eds.), Proceedings of the 1990 ACM SIGMOD International Conference on Management of Data, SIGMOD Record 2, ACM Press, New York, 1990, 322–331. [Cor99] O. Corporation: Dokumentation zu Oracle 8i: PL/SQL User’s Guide and Reference, 1999. [Gut84] A. Guttman: R-Trees: A Dynamic Index Structure for Spatial Searching. In B. Yormark (ed.), Proceedings of the 1984 ACM SIGMOD International Conference on Management of Data, SIGMOD Record 2, ACM Press, New York, 1984, 47–57. [Klo99] S. Klopp. Implementation von R∗ -Bäumen als benutzerdefinierte Indexstruktur in Oracle 8i. Studienarbeit, Institut für Informatik, Universität Hannover, Hannover, 1999. [OM84] J. A. Orenstein, T. H. Merrett: A Class of Data Structures for Associative Searching. In Proceedings of the 4th ACM SIGACT-SIGMOD-SIGART Symposium on Principles of Database Systems, ACM Press, New York, 1984, 181 – 190. [Ore86] J. A. Orenstein: Spatial Query Processing in an Object-Oriented Database System. In C. Zaniolo (ed.), Proceedings of the 1986 ACM SIGMOD International Conference on Management of Data, SIGMOD Record 2, ACM Press, New York, 1986. [OW96] T. Ottmann, P. Widmayer: Algorithmen und Datenstrukturen (3. Aufl.). Informatik, Spektrum, Heidelberg, 1996. 60