intersects

Werbung
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
Herunterladen