Ulrich Kern Hermann Götz Martin Jakobi

Transcrição

Ulrich Kern Hermann Götz Martin Jakobi
Inhaltsverzeichnis
TDB-7
Das Programmierhandbuch
Autor:
Ulrich Kern
Hermann Götz
Martin Jakobi
Mit freundlichen Mithilfe von Anna Pakosch (Australien), Julia Shelikhow (Schwabach) und Hartmut Streich (München)
-1-
Inhaltsverzeichnis
Tdbengine 7.06
uKern: 24.10.2013: neue Funktion clearCache(db)
bei setalias: löscht den Cache und stellt sicher, dass Alias nicht aus dem Cache liest.
Convline ist nicht beschrieben – 22.11.2013/hg
Vorwort:
Wir haben es uns zum Ziel gesetzt, eine vollständige Dokumentation zur tdbengine zu erstellen.
Dazu haben wir erst einmal alle existenten Texte gesammelt und zusammen getragen.
Die Strukturierung und Überarbeitung der einzelnen Kapitel geht nicht so einfach wie ursprünglich vorgestellt.
Teilweise sind die bestehenden Texte nicht ganz richtig und so muss man jede Aussage prüfen.
Nichts desto trotz arbeiten wir uns vorwärts.
Die Kapitel zum dojo Interface sind noch fragmentarisch, ist aber auch erst für die eigentliche Programmierung mit tdb7 relevant.
Zuerst einmal ist die Aufarbeitung der bestehenden Funktionen in der tdbengine selbst relevant.
Hier ist die Vorstellung, dass nicht nur ein Stichwort zu einer Funktion steht, sondern Beispiele hinterlegt werden, aus der man erkennen kann, was man mit der
Funktion machen kann.
Wenn jemand Anmerkungen zu einzelnen Funktionen hat, so sind wir dafür sehr dankbar.
Wir haben schon einen kleinen Kreis an Mithelfern gewonnen, die uns dadurch unterstützen.
Schwabach, den 22.11.2011
Hermann Götz
Offene Fragen:
-2-
Inhaltsverzeichnis
1. Wie man das Indexkrennzeichen umdefinieren? Default ist dies , es soll aber optional z.B. ; sein.
1.
Meeting 10.06.2013 ................................................................................................................................................................................................................ - 24 -
2.
Bekannte Bugs ........................................................................................................................................................................................................................ - 26 2.1
FindAndMark und utime-Feld im Index ............................................................................................................................................................................ - 26 -
3.
CGI-Anwendungen ohne Apache-Konfiguration..................................................................................................................................................................... - 27 -
4.
Rechte für Apache User (Linux only) ...................................................................................................................................................................................... - 29 -
5.
4.1
Verzeichnis, welches die tdbengine beinhaltet ................................................................................................................................................................ - 29 -
4.2
Database (bzw. sysbase) ................................................................................................................................................................................................ - 29 -
4.3
Program (Verzeichnis mit den prg-Dateien) ................................................................................................................................................................... - 29 -
Tabelle anlegen....................................................................................................................................................................................................................... - 31 5.1
Tabellendefinition Abschnitt [Structure] ........................................................................................................................................................................... - 31 -
5.1.1
Beispiel für Adressdatenbank: ................................................................................................................................................................................. - 31 -
5.1.2
Beispiel für eine Medikamentendatei (z.B. mit vielen Select Feldern) .................................................................................................................... - 36 -
5.1.3
Beispiel für die Verwendung eines Blob-Feldes ...................................................................................................................................................... - 37 -
5.2
Tabellendefinition Abschnitt [Index] ................................................................................................................................................................................. - 38 -
5.1
Tabelle anlegen - MakeDB .............................................................................................................................................................................................. - 38 5.1.1.1
Zeichenkette ........................................................................................................................................................................................................ - 41 -
5.1.1.2
Zahlen .................................................................................................................................................................................................................. - 42 -
5.1.1.3
Memos und Blobs ................................................................................................................................................................................................ - 42 -
5.1.1.4
AUTO-INCREMENT ............................................................................................................................................................................................ - 43 -
5.1.1.5
Datum und Zeit .................................................................................................................................................................................................... - 43 -
5.1.1.6
Aufzählung ........................................................................................................................................................................................................... - 43 -
5.1.1.7
Verknüpfungen..................................................................................................................................................................................................... - 43 -
5.1.1.8
IMPORTFELDER FESTLEGEN .......................................................................................................................................................................... - 43 -3-
Inhaltsverzeichnis
5.2
Öffnen und Schliessen von Datenbanken ....................................................................................................................................................................... - 45 -
5.2.1
5.2.1.1
5.2.2
Tabelle virtuell öffnen – opendb im Modus 271 ................................................................................................................................................... - 48 closedb() - Schliesst eine Tabelle ............................................................................................................................................................................ - 49 -
5.3
reopendb() - Öffnet eine bereits geöffnete Tabelle mit anderen Rechten ....................................................................................................................... - 50 -
5.4
setalias() - Tabelle mehrfach öffnen ................................................................................................................................................................................ - 50 -
5.5
Informationen zu einer geöffneten Datenbank auslesen .................................................................................................................................... - 51 -
5.5.1
dbno() - Liefert das TableHandle ............................................................................................................................................................................. - 51 -
5.5.2
dbrights() - Rechte an geöffneter Tabelle ................................................................................................................................................................ - 52 -
5.5.3
dbdir() - Verzeichnisteil des Pfades einer Tabelle ................................................................................................................................................... - 52 -
5.5.4
dbname() - Namensteil des Pfades einer Tabelle ................................................................................................................................................... - 53 -
5.5.5
getstructure() - Struktur eines Tabellen-Feldes ermitteln ........................................................................................................................................ - 53 -
5.5.6
gettype() - Feldtyp ermitteln (veraltet) ..................................................................................................................................................................... - 55 -
5.5.7
filesize() - Anzahl der Datensätze einer Tabelle ...................................................................................................................................................... - 56 -
5.5.8
getmode() - Tabellenrechte ermitteln (veraltet) ....................................................................................................................................................... - 56 -
5.5.9
filemode() - Liefert die Zugriffsrechte an einer Tabelle (veraltet) ............................................................................................................................ - 57 -
5.6
6.
opendb() - Öffnen einer Tabelle ....................................................................................................................................................................... - 45 -
Allgemeine Datenbankfunktionen .................................................................................................................................................................................... - 57 -
5.6.1
primtable() - Setzt die Primärtabelle ........................................................................................................................................................................ - 57 -
5.6.2
relation() ................................................................................................................................................................................................................... - 58 -
5.6.3
Tabellenstruktur ermitteln ........................................................................................................................................................................................ - 59 -
5.6.4
getpw() - Passwort einer Tabelle ermitteln .............................................................................................................................................................. - 59 -
5.6.5
getcode() - Verschlüsselungscode einer Tabelle .................................................................................................................................................... - 59 -
5.6.6
deldb() - Löscht eine Tabelle inklusive aller dazugehörenden Elemente. ............................................................................................................... - 61 -
5.6.7
rendb() - Gibt einer Tabelle einen neuen Dateinamen ............................................................................................................................................ - 61 -
5.6.8
cleardat() - Löscht alle Datensätze einer Datei ....................................................................................................................................................... - 62 -
Datensätze lesen, schreiben und löschen .............................................................................................................................................................................. - 63 6.1
ReadRec(Tabellennummer,Satznummer) : Integer ........................................................................................................................................................ - 63 -4-
Inhaltsverzeichnis
7.
8.
9.
6.2
WriteRec(Tabellenummer,Satznummer) : Integer ........................................................................................................................................................... - 63 -
6.3
DelRec(Tabellennummer,Satznummer) : Integer ............................................................................................................................................................ - 64 -
6.4
AutoField(Tabellennummer) : Integer .............................................................................................................................................................................. - 64 -
6.5
AutoRecNo(db : Integer) : Integer ................................................................................................................................................................................... - 64 -
6.6
Datensatz kopieren – 19.10.2012 .................................................................................................................................................................................... - 65 -
Datensatz suchen ................................................................................................................................................................................................................... - 66 7.1
FindRec............................................................................................................................................................................................................................ - 66 -
7.2
Suche über hierarchischen Index .................................................................................................................................................................................... - 66 -
7.3
FirstRec............................................................................................................................................................................................................................ - 67 -
7.4
NextRec ........................................................................................................................................................................................................................... - 67 -
7.5
PrevRec ........................................................................................................................................................................................................................... - 68 -
Datensätze in einer Schleife auslesen – 02.12.2011.............................................................................................................................................................. - 69 8.1
Sub / Endsub ................................................................................................................................................................................................................... - 69 -
8.2
Firstrec / Nextrec ............................................................................................................................................................................................................. - 72 -
8.3
Über Array........................................................................................................................................................................................................................ - 73 -
Indexfunktionen ....................................................................................................................................................................................................................... - 75 9.1
ALLGEMEINES ZU INDIZES .......................................................................................................................................................................................... - 75 -
9.2
DIE INDIZES DES TDBENGINE ..................................................................................................................................................................................... - 75 -
9.2.1
Hierarchischer Index ................................................................................................................................................................................................ - 76 -
9.2.2
Berechneter Index ................................................................................................................................................................................................... - 76 -
9.3
Index erzeugen ................................................................................................................................................................................................................ - 76 -
9.3.1
genindex() Erzeugt einen neuen Index ................................................................................................................................................................... - 77 -
9.4
Index löschen ................................................................................................................................................................................................................... - 78 -
9.5
Index auffrischen ............................................................................................................................................................................................................. - 78 -
9.6
Alle Indizes auffrischen .................................................................................................................................................................................................... - 78 -
9.7
Indexbeschreibung ermitteln............................................................................................................................................................................................ - 79 -
9.7.1
inddef() - Indexbeschreibung ermitteln .................................................................................................................................................................... - 79 -
-5-
Inhaltsverzeichnis
9.8
indname() - Liefert den Namen eines Index .................................................................................................................................................................... - 79 -
9.9
regenall() - Regeneriert sämtliche Indizes einer Tabelle ................................................................................................................................................. - 80 -
9.10
regenind() - Regeneriert den Index einer Tabelle............................................................................................................................................................ - 80 -
9.11
indno() - Nummer des aktiven Index ermitteln (veraltet) ................................................................................................................................................. - 81 -
9.12
access() - Legt die Zugriffsreihenfolge für eine Tabelle fest ........................................................................................................................................... - 82 -
9.13
delindex() - Löscht einen bestehenen Index und entfernt ihn aus der Tabelle ............................................................................................................... - 83 -
9.14
treeinfo() - Index-Informationen ....................................................................................................................................................................................... - 84 -
10.
ZUGRIFFSREIHENFOLGE ERMITTELN UND FESTLEGEN ............................................................................................................................................ - 85 -
10.1
DATENSATZ ÜBER INDEX SUCHEN ............................................................................................................................................................................ - 85 -
10.1.1
FILTER SETZEN ..................................................................................................................................................................................................... - 88 -
10.1.2
DIE FILTER-AUTOMATIK DER TDBENGINE ........................................................................................................................................................ - 88 -
11.
Volltextindizierung ................................................................................................................................................................................................................ - 90 -
11.1
Volltextdatein anlegen (mit Hilfe des baseman.prg) ........................................................................................................................................................ - 91 -
11.1.1
11.2
Interne Informationen zum manuellen Aufbau eines Volltextes .............................................................................................................................. - 92 -
Volltext komplett auffrischen - FulltextScanRecs ............................................................................................................................................................ - 93 -
11.2.1
ScanRecs(D,I,R,Fields(Felder),ExtABC,Cut,Kontraindex,Modus,Step,MaskenFeld,DynKontraIndex) : Integer ................................................... - 94 -
11.2.2
DIE OPTIONALEN PARAMETER ........................................................................................................................................................................... - 95 -
11.2.2.1
ExtABC ............................................................................................................................................................................................................ - 95 -
11.2.2.2
Cut ................................................................................................................................................................................................................... - 95 -
11.2.2.3
Kontraindex ...................................................................................................................................................................................................... - 96 -
11.2.2.4
Modus .............................................................................................................................................................................................................. - 96 -
11.2.2.5
Step .................................................................................................................................................................................................................. - 96 -
11.2.2.6
Maskenfeld....................................................................................................................................................................................................... - 97 -
11.2.2.7
DynKontraIndex ............................................................................................................................................................................................... - 98 -
11.2.3
Fehlerbehandlung .................................................................................................................................................................................................... - 98 -
11.3
Einzelnen Datensatz indizieren mit FulltextScanRec ...................................................................................................................................................... - 98 -
11.4
Den Volltext für einen einzelnen Datensatz löschen - FulltextUnScanRec ..................................................................................................................... - 99 -6-
Inhaltsverzeichnis
11.5
Die Suche im Volltextindex ............................................................................................................................................................................................ - 101 -
11.5.1
Beispiele: ............................................................................................................................................................................................................... - 102 -
11.5.2
OPS ....................................................................................................................................................................................................................... - 102 -
11.5.2.1
Maskenzahl .................................................................................................................................................................................................... - 102 -
11.5.2.2
Bitfeld ........................................................................................................................................................................................................... - 103 -
11.5.2.3
Abbildung auf die Markierungsliste ................................................................................................................................................................ - 103 -
11.5.2.4
Direkter Zugriff auf das Bitfeld ....................................................................................................................................................................... - 104 -
11.5.3
REALTIONSTABELLEN ........................................................................................................................................................................................ - 104 -
11.5.4
DOKUMENTENARCHIV ........................................................................................................................................................................................ - 104 -
12.
Der Zugriff auf Datenfelder ................................................................................................................................................................................................ - 110 -
12.1
GetField und GetRField ................................................................................................................................................................................................. - 110 -
12.2
SetField und SetRField .................................................................................................................................................................................................. - 110 -
13.
Übersicht über alle Tabellenfunktionen ............................................................................................................................................................................. - 113 -
14.
Der Zugriff auf Memofelder – 18.11.2011.......................................................................................................................................................................... - 116 -
14.1
Memo einlesen – ReadMemo() ..................................................................................................................................................................................... - 116 -
14.1.1
Syntax .................................................................................................................................................................................................................... - 116 -
14.1.2
Ergebnis ................................................................................................................................................................................................................. - 116 -
14.1.3
Beispiele ................................................................................................................................................................................................................ - 117 -
14.2
Memogröße bestimmen – MemoLen() .......................................................................................................................................................................... - 117 -
14.3
MEMOS AUS HTML-FORMUAREN EINLESEN........................................................................................................................................................... - 118 -
14.4
Memo auslesen – CopyMemo()..................................................................................................................................................................................... - 118 -
14.5
MEMOS IN TEMPLATES .............................................................................................................................................................................................. - 118 -
14.6
SUCHE IN MEMOS ....................................................................................................................................................................................................... - 119 -
15.
Speichern von binären Daten in Blob Dateien................................................................................................................................................................... - 120 -
15.1
blobsize() - Liefert die Größe eines BLOBs ................................................................................................................................................................... - 120 -
15.2
blobtype() - Liefert den Typ des Inhalts eines BLOB-Feldes. (nicht mehr relevant) .................................................................................................... - 120 -
15.3
copyblob() - Kopiert den (eingebetteten) Inhalt eines BLOB-Feldes in eine Datei oder ein Array ................................................................................ - 121 -7-
Inhaltsverzeichnis
15.4
Blob einlesen – EmbedBlob() ........................................................................................................................................................................................ - 122 -
15.5
Wichtige Hinweise zur Verwendung von blob-Feldern .................................................................................................................................................. - 123 -
16.
Datensätze markieren........................................................................................................................................................................................................ - 124 -
16.1
setmark() - Setzt eine Marke in der Markierungsliste .................................................................................................................................................... - 124 -
16.2
setmarks() - Markiert alle Datensätze einer Tabelle ...................................................................................................................................................... - 124 -
16.3
delmark() - Löscht eine Satznummer aus der Markierungsliste einer Tabelle .............................................................................................................. - 125 -
16.4
revmarks() - Dreht die Ordnung der Markierungsliste einer Tabelle um ....................................................................................................................... - 125 -
16.5
delmarkedrecords() - Löscht alle Datensätze aus der Markierungsliste. ...................................................................................................................... - 125 -
16.6
delmarks() - Löscht die gesamte Markierungsliste einer Tabelle .................................................................................................................................. - 127 -
16.7
ismark() - Datensatz in Markierungsliste prüfen ............................................................................................................................................................ - 127 -
16.8
findandmark() - markiert eine Auswahl (Selektion)........................................................................................................................................................ - 128 -
16.9
markdoubles() - Markiert doppelte Einträge bzgl. eines Index ...................................................................................................................................... - 129 -
16.10
markindex() - Sortiert die Markierungsliste nach einem Index .................................................................................................................................. - 129 -
16.11
markpattern() ............................................................................................................................................................................................................. - 130 -
16.12
andmarks() - Bildet den Mengendurchschnitt zwischen Markierungsliste und Array ................................................................................................ - 131 -
16.13
firstmark[() - Liefert die physikalische Satznummer des Anfangs der Markierungsliste einer Tabelle ...................................................................... - 133 -
17.
Mathematische Funktionen................................................................................................................................................................................................ - 134 -
17.1
Native Funktionen .......................................................................................................................................................................................................... - 134 -
17.1.1
Quadratwuzel –SQRT() ......................................................................................................................................................................................... - 134 -
17.1.2
Absolutbetrag –ABS() ............................................................................................................................................................................................ - 134 -
17.1.3
Arkuskosinus einer Zahl - ARCCOS() ................................................................................................................................................................... - 135 -
17.1.4
Kosinus einer Zahl – COS() ................................................................................................................................................................................... - 135 -
17.1.5
Exponential-Funktion - EXP() ................................................................................................................................................................................ - 135 -
17.1.6
Gebrochener Anteil einer Zahl - FRAC() ............................................................................................................................................................... - 136 -
17.1.7
Ganzzahliger Anteil einer Zahl – INT() .................................................................................................................................................................. - 136 -
17.1.8
natürlicher Logaritmus - LOG() .............................................................................................................................................................................. - 137 -
17.1.9
Zufallszahl – RANDOM() ....................................................................................................................................................................................... - 137 -
-8-
Inhaltsverzeichnis
17.1.10
Rundet eine Zahl kaufmännisch auf n Stellen hinter dem Komma – ROUND() ................................................................................................ - 138 -
17.1.11
Sinus – SIN()...................................................................................................................................................................................................... - 138 -
17.1.12
Zahl in String konvertieren – STR() ................................................................................................................................................................... - 139 -
17.1.13
Testet, ob ein Bit gesetzt ist – TESTBIT() – 21.03.2013 ................................................................................................................................... - 140 -
17.2
Funktionen in easy geschrieben .................................................................................................................................................................................... - 144 -
17.2.1
Potenztieren ........................................................................................................................................................................................................... - 144 -
17.2.2
Maximumwert......................................................................................................................................................................................................... - 144 -
17.2.3
Minimumwert.......................................................................................................................................................................................................... - 144 -
17.2.4
Zufallszahl zwischen l und h .................................................................................................................................................................................. - 148 -
18.
Laufzeitschalter der tdbengine........................................................................................................................................................................................... - 156 -
18.1
Alle Schalter in der Übersicht ........................................................................................................................................................................................ - 156 -
18.2
Setzen und Auslesen ..................................................................................................................................................................................................... - 157 -
18.3
SetPara(Schalter:STRING):REAL ................................................................................................................................................................................. - 157 -
18.4
... und im Detail .............................................................................................................................................................................................................. - 157 -
18.4.1
ak ........................................................................................................................................................................................................................... - 157 -
18.4.2
ec ........................................................................................................................................................................................................................... - 158 -
18.4.3
em .......................................................................................................................................................................................................................... - 159 -
18.4.4
de ........................................................................................................................................................................................................................... - 159 -
18.4.5
nb ........................................................................................................................................................................................................................... - 160 -
18.4.6
nv ........................................................................................................................................................................................................................... - 161 -
19.
Weitere Funkionen unsortiert............................................................................................................................................................................................. - 162 -
19.1
autofield() - Liefert die Feldnummer des Feldes, das die Autonummer speichert ......................................................................................................... - 162 -
19.2
copymemo() - Kopiert ein Memo in eine (externe) Textdatei ........................................................................................................................................ - 162 -
19.3
delrec() - Löscht einen Datensatz physikalisch aus einer Tabelle ................................................................................................................................ - 163 -
19.4
editoff() - Satzsperre freigeben ...................................................................................................................................................................................... - 163 -
19.5
editon() - Satzsperre einrichten ..................................................................................................................................................................................... - 164 -
19.6
exists() - Prüft die Erfüllbarkeit einer Bedingung ........................................................................................................................................................... - 167 -
-9-
Inhaltsverzeichnis
19.7
fields() - Feld-Definition für Volltextindizierung .............................................................................................................................................................. - 168 -
19.8
fileno() - Liefert Handle der aktuellen Primärtabelle ...................................................................................................................................................... - 169 -
19.9
findauto() - Liefert die physikalische Satznummer zu einer AUTO-Nummer ................................................................................................................ - 170 -
19.10
findrec() - Sucht einen Datensatz über einen Index .................................................................................................................................................. - 171 -
19.11
firstrec() - Liefert die Nummer des ersten Datensatzes bzgl. aktuellem Zugriff ........................................................................................................ - 172 -
19.12
fsum() - Addiert die Feldinhalte aufeinanderfolgender Datenfelder ........................................................................................................................... - 174 -
19.13
getdef() - Schreibt die Struktur einer Tabelle in eine Textdatei ................................................................................................................................. - 175 -
19.14
getfield() - liefert den Inhalt eines Feldes aus dem Satzpuffer .................................................................................................................................. - 175 -
19.14.1
Zugriff auf den Inhalt eines Arrays ..................................................................................................................................................................... - 176 -
19.15
getmarks() - speichert eine Markierungsliste ............................................................................................................................................................. - 176 -
19.16
getrec() - Schreibt den Satzpuffer in ein Array .......................................................................................................................................................... - 179 -
19.16.1
Sonderfall neuer Datensatz ............................................................................................................................................................................... - 180 -
19.17
getrfield() - liefert den Inhalt eines Feldes aus dem Satzpuffer ................................................................................................................................. - 182 -
19.18
incrfield() - Incrementiert den Wert eines Datenfeldes .............................................................................................................................................. - 183 -
19.19
Feldbezeichner ermitteln – label() ............................................................................................................................................................................. - 183 -
19.19.1
Ergebnis von label() ........................................................................................................................................................................................... - 184 -
19.20
Feldnummer ermitteln - labelno() – 18.11.2011 ........................................................................................................................................................ - 184 -
19.21
lastrec() - Nummer des letzten Datensatzes bzgl aktuellem Zugriff .......................................................................................................................... - 185 -
19.22
link() ........................................................................................................................................................................................................................... - 186 -
19.23
linkblob() - verknüpft eine externe Datei mit einem BLOB-Feld ................................................................................................................................ - 186 -
19.24
linkinfo() ..................................................................................................................................................................................................................... - 187 -
19.25
lock() - Tabellensperre einrichten .............................................................................................................................................................................. - 187 -
19.26
maxfile() - größter gültiger Tabellenhandle ................................................................................................................................................................ - 189 -
19.27
maxlabel() - Anzahl der Datenfelder (Spalten) in einer Tabelle ................................................................................................................................ - 189 -
19.28
memolen() - Anzahl der Zeichen in einem Memo-Feld ermitteln .............................................................................................................................. - 191 -
19.29
memostr() - Anfang eines Memo-Feldes als String ................................................................................................................................................... - 191 -
19.30
newtable() - Kopie (leer) einer Tabelle erzeugen ...................................................................................................................................................... - 192 -
- 10 -
Inhaltsverzeichnis
19.31
nextmark() - nächster markierter Datensatz .............................................................................................................................................................. - 192 -
19.32
nextrec() - Physikalische Satznummer des Nachfolgers ........................................................................................................................................... - 193 -
19.33
nmarks() - Größe der Markierungsliste eines Tabellenhandle .................................................................................................................................. - 194 -
19.34
prevrec() - Physikalische Satznummer des Vorgängers ........................................................................................................................................... - 195 -
19.35
putmarks() - Setzen der Markierungsliste einer Tabelle ............................................................................................................................................ - 196 -
19.36
putrec() - Schreibt ein Array in den Satzpuffer .......................................................................................................................................................... - 196 -
19.37
readrec() - Liest einen Datensatz .............................................................................................................................................................................. - 198 -
19.38
recno() - Nummer des aktuellen Datensatzes einer Tabelle ..................................................................................................................................... - 198 -
19.39
recnr() - Nummer des aktuellen Datensatzes einer Tabelle (veraltet) ....................................................................................................................... - 199 -
19.40
setauto() - Setzen der nächsten AUTO-Nummer ...................................................................................................................................................... - 199 -
19.41
setfield() - Übertragung von Informationen in den Satzpuffer ................................................................................................................................... - 199 -
19.42
setfields() - mehrere Felder eines Datensatzes belegen ........................................................................................................................................... - 201 -
19.43
setfilter() - Setzt einen Filter auf den aktiven Index ................................................................................................................................................... - 201 -
19.43.1
Filteranweisungen .............................................................................................................................................................................................. - 202 -
19.43.2
Filter mit gleichzeitigem Markieren der Datensätze........................................................................................................................................... - 202 -
19.43.3
Häufige Programmierfehler im Zusammenhang mit setfilter() ........................................................................................................................... - 203 -
19.43.4
Risikobewertung bei der Verwendung von setfilter ........................................................................................................................................... - 204 -
19.44
setrecord() - Kopiert einen Datensatz aus einer anderen Tabelle ............................................................................................................................. - 205 -
19.45
setrfield() - Übertragung von numerischen Informationen in den Satzpuffer ............................................................................................................ - 206 -
19.46
sortmark() - sortiert die Markierungsliste ................................................................................................................................................................... - 206 -
19.47
unlock() - Sperre freigeben ........................................................................................................................................................................................ - 207 -
19.48
Schreibt einen Datensatz - writerec() - 05.12.2011 ................................................................................................................................................... - 207 -
19.48.1
Beispielprogramm für gepuffertes Speichern ................................................................................................................................................... - 208 -
19.49
flushdb() - Leert den Schreibpuffer ............................................................................................................................................................................ - 210 -
19.50
flush() - Übertragung sämtlicher gepufferter Daten ................................................................................................................................................... - 211 -
20.
20.1
Betriebssystemfunktionen.................................................................................................................................................................................................. - 212 Zugriff auf Verzeichnisse ............................................................................................................................................................................................... - 212 -
- 11 -
Inhaltsverzeichnis
20.1.1
Aktuelles Verzeichnis ermitteln - GetDir ................................................................................................................................................................ - 212 -
20.1.2
chmod() - Dateirechte ändern ................................................................................................................................................................................ - 212 -
20.1.3
Verzeichnis einer TDB-Tabelle ermitteln - DBDir .................................................................................................................................................. - 213 -
20.1.4
Projektverzeichnis ermitteln – BaseDir ?? ............................................................................................................................................................. - 213 -
20.1.5
Verzeichnis anlegen - MakeDir .............................................................................................................................................................................. - 213 -
20.1.6
Verzeichnis löschen – RemDir ??? ....................................................................................................................................................................... - 213 -
20.1.7
Verzeichnis auf Existenz abprüfen ........................................................................................................................................................................ - 214 -
20.2
Dateioperationen ........................................................................................................................................................................................................... - 214 -
20.2.1
Datei kopieren – CopyFile() ................................................................................................................................................................................... - 214 -
20.2.2
Datei löschen – DelFile() ....................................................................................................................................................................................... - 214 -
20.2.3
Datei auf Existenz prüfen – IsFile() ....................................................................................................................................................................... - 214 -
20.2.4
Verzeichniseintrag suchen - FirstDir ...................................................................................................................................................................... - 214 -
20.2.5
Nächsten Verzeichniseintrag suchen - NextDir ..................................................................................................................................................... - 215 -
20.2.6
Freien Speicherplatz auf der Festplatte ermitteln - DiskFree ................................................................................................................................ - 215 -
20.2.7
Umgebungsvariable auslesen - GetEnv ................................................................................................................................................................ - 216 -
21.
Lesen und Schreiben von Textdateien .............................................................................................................................................................................. - 216 -
21.1
Textdatei zum Lesen öffnen - Reset .............................................................................................................................................................................. - 217 -
21.2
Textdatei zum Schreiben öffnen - Rewrite .................................................................................................................................................................... - 217 -
21.3
Text an Datei anhängen - TAppend .............................................................................................................................................................................. - 217 -
21.4
Texthandle schließen - Close ........................................................................................................................................................................................ - 217 -
21.5
Zeichen aus einer Textdatei lesen - Read ..................................................................................................................................................................... - 217 -
21.6
Zeile aus einer Textdatei lesen - ReadLn ...................................................................................................................................................................... - 217 -
21.7
Text in Textdatei schreiben - Write ................................................................................................................................................................................ - 217 -
21.8
Zeile in Textdatei schreiben - WriteLn ........................................................................................................................................................................... - 218 -
21.9
Textende erkennen -EOT .............................................................................................................................................................................................. - 218 -
21.9.1
22.
Beispiel:Textdatei lesen ......................................................................................................................................................................................... - 218 -
Streamfunktionen ............................................................................................................................................................................................................... - 221 -
- 12 -
Inhaltsverzeichnis
22.1
f_create(filename : string) : integer ................................................................................................................................................................................ - 221 -
22.2
f_close(handle : integer) : integer .................................................................................................................................................................................. - 221 -
22.3
f_seek(handle, pos : integer) : integer ........................................................................................................................................................................... - 221 -
22.4
f_read(handle : integer; var array [; nbytes : integer) : integer ...................................................................................................................................... - 222 -
22.5
f_pos .............................................................................................................................................................................................................................. - 222 -
22.6
f_close() Schließt einen neuen Stream ......................................................................................................................................................................... - 222 -
22.7
f_error() Liefert den Fehlercode des Betriebssystems für die letzte Operation ............................................................................................................. - 223 -
22.8
f_find() Findet String ...................................................................................................................................................................................................... - 223 -
22.9
f_replace() Ersetzt target durch replacement ................................................................................................................................................................ - 223 -
22.10
f_seek() Positioniert in einem Stream ........................................................................................................................................................................ - 223 -
22.11
f_size() Ermittelt die Größe eines Stream ................................................................................................................................................................ - 223 -
22.12
LoadTemplateviaProxy .............................................................................................................................................................................................. - 223 -
23.
Systemfunktionen .......................................................................................................................................................................................................... - 224 -
23.1
chdir() – Verzeichniswechsel ......................................................................................................................................................................................... - 225 -
23.2
compile() - Compiliert einen EASY-Quelltext in den PRG-Zwischencode .................................................................................................................... - 226 -
23.3
copyfile() - Legt die Kopie einer Datei an ...................................................................................................................................................................... - 226 -
23.4
delfile() - Löscht eine externe Datei ............................................................................................................................................................................... - 228 -
23.5
delident() - Löscht einen Eintrag aus einer Konfigurationsdatei .................................................................................................................................... - 228 -
23.6
dirinfo() - Verzeichniseintrag für eine Datei ................................................................................................................................................................... - 229 -
23.7
diskfree() - Freien Platz auf Speichermedium ermitteln ................................................................................................................................................ - 229 -
23.8
DO.................................................................................................................................................................................................................................. - 230 -
23.9
execprog() - Kompilert und führt beliebigen EASY-Code zur Laufzeit aus ................................................................................................................... - 230 -
23.10
firstdir() -Liefert den ersten Verzeichniseintrag zu einem Suchmuster...................................................................................................................... - 231 -
23.11
getdir() - ermittelt das aktuelle Verzeichnis ............................................................................................................................................................... - 232 -
23.12
getenv() Kurz Environment-Variable ermitteln ........................................................................................................................................................... - 232 -
23.13
getident() – Holt einen Eintrag aus einer Konfigurationsdatei ................................................................................................................................... - 234 -
23.14
getpara() Kurz Wert eines Laufzeitschalters ermitteln............................................................................................................................................... - 236 -
- 13 -
Inhaltsverzeichnis
23.15
getsize() - Liefert die Größe einer Datei .................................................................................................................................................................... - 237 -
23.16
halt() - Programm benden .......................................................................................................................................................................................... - 237 -
23.17
isfile() - prüft Existenz einer Datei oder eines Verzeichnisses .................................................................................................................................. - 238 -
23.18
makedir() - legt ein neues Verzeichnis an ................................................................................................................................................................. - 238 -
23.19
nextdir() - nächster Verzeichniseintrag bzgl FirstDir .................................................................................................................................................. - 239 -
23.20
Funktion privdir() - Gibt absoluten Pfad zur tdbengine zurück .................................................................................................................................. - 239 -
23.21
remdir() - Löscht ein (leeres) Verzeichnis .................................................................................................................................................................. - 240 -
23.22
rename() -Gibt einer Datei einen neuen Namen ....................................................................................................................................................... - 240 -
23.23
setpara() - Setzt einen Laufzeitschalter ..................................................................................................................................................................... - 242 -
23.24
testident() - Prüft, ob ein Eintrag in einer Konfigurationsdatei vorhanden ist ............................................................................................................ - 243 -
23.25
timeout() - (nur Linux) Bricht die Ausführung einer Prozedur nach abgelaufener Frist ab ........................................................................................ - 243 -
23.26
today() - Kurz Systemdatum ...................................................................................................................................................................................... - 244 -
23.27
varname() - Liefert den Deklarationsnamen einer Variablen ..................................................................................................................................... - 244 -
Ramtexte – das Arbeiten mit virtuellen Textdateien .......................................................................................................................................................... - 246 -
24.
24.1
Ramtext:~plain – 10.06.2013......................................................................................................................................................................................... - 246 -
24.2
Allgemeine Information zu Ramtexten ........................................................................................................................................................................... - 246 -
24.3
Ramtext(Filename : STRING; InitSize : INTEGER) - Initialisierung eines Ramtext ...................................................................................................... - 248 -
24.4
Für Ramtexte gibt es einen Satz an Funktionen – 24.11.2011 ..................................................................................................................................... - 248 -
24.4.1
ramtext_find(ramtext,string[,startpos]) : findpos .................................................................................................................................................... - 248 -
24.4.2
ramtext_insert(ramtext,startpos,string) : moved_chars ......................................................................................................................................... - 248 -
24.4.3
ramtext_delete(ramtext : STRING; startpos, anzahl : INTEGER) : INTEGER ...................................................................................................... - 249 -
24.4.4
ramtext_copy(ramtext:STRING;startpos,anzahl:INTEGER):INTEGER ................................................................................................................ - 249 -
24.4.5
ramtext_paste(ramtext:STRING;startpos:INETEGER):INTEGER ........................................................................................................................ - 249 -
24.4.6
ramtext_part(ramtext,startpos,count) : string ......................................................................................................................................................... - 250 -
24.4.7
ramtext:~clip .......................................................................................................................................................................................................... - 250 -
24.4.8
ramtext_name(hdl : INTEGER).............................................................................................................................................................................. - 250 -
24.4.9
ramtext_subst(ramtext : STRING; target : STRING; substitution [; mode] : INTEGER) : INTEGER .................................................................... - 250 -
- 14 -
Inhaltsverzeichnis
24.4.10
subst(target : STRING; (replacement : STRING | db : INTEGER; field)[; mode : INTEGER]) : INTEGER ....................................................... - 251 -
24.4.11
Getsize(s:String):Integer .................................................................................................................................................................................... - 253 -
24.5
PraxisBeispiel: Verwendung von Ramtexten................................................................................................................................................................. - 254 -
24.5.1
Eingabeformular für ein Adressformuar ................................................................................................................................................................. - 254 -
24.5.2
Praxis dynamisches Ausführen von Quellcode, der in einem Ramtext steht ........................................................................................................ - 260 -
Lesen und schreiben in Konfigurationsdateien – 13.11.2011 ........................................................................................................................................... - 261 -
25.
25.1
Neuen Wert in Konfigurationsdatei setzen - setident .................................................................................................................................................... - 261 -
25.2
Holt einen Eintrag aus einer Konfigurationsdatei holen – getident() ............................................................................................................................. - 263 -
25.3
Bufferung von INI-Dateien ............................................................................................................................................................................................. - 264 -
26.
Array Funktionen ............................................................................................................................................................................................................... - 266 -
26.1
bitand() -Schnittmenge zweier Bitfelder ......................................................................................................................................................................... - 267 -
26.2
bitandnot() - Schnitt eines Bitfeldes mit dem Kompliment eines zweiten ...................................................................................................................... - 267 -
26.3
bitnot() - Komplement eines Bitarray ............................................................................................................................................................................. - 268 -
26.4
bitor() - Vereinigungsmenge zweier Bitfelder ................................................................................................................................................................ - 269 -
26.5
bitsum() - Gibt die Summe übereinstimmender Bitwerte aus mehreren Arrays ............................................................................................................ - 269 -
26.6
Schneidet ein TBit-Array exakt - bittrunc() (21.11.2011) .............................................................................................................................................. - 270 -
26.7
clrarray() - Initialisiert eine Array-Variable mit leeren Einträgen .................................................................................................................................... - 271 -
26.8
getauto() - Kopiert den inr-Index in ein Array ................................................................................................................................................................ - 271 -
26.9
getmarksauto() - Kopiert die Autonummer markierter Datensätze in ein Integerarray ................................................................................................. - 273 -
26.10
high() - Felddimension ermitteln ................................................................................................................................................................................ - 273 -
26.11
Inarray () -prüft, ob Zahl in Zahlenfeld ....................................................................................................................................................................... - 275 -
26.12
inarraypos() - InArray mit Rückgabe der Position ...................................................................................................................................................... - 275 -
26.13
initarray() - dynamische Felddimensionierung ........................................................................................................................................................... - 276 -
26.14
nbits() - Zählt die gesetzten Bits ................................................................................................................................................................................ - 276 -
26.15
putmarksauto() ........................................................................................................................................................................................................... - 277 -
26.16
strsort() - Sortiert ein- und zweidimensionale STRING-Arrays .................................................................................................................................. - 277 -
26.16.1
Sortierung zweidimensionale String-Felder: ...................................................................................................................................................... - 277 -
- 15 -
Inhaltsverzeichnis
27.
Funktionen zum Sessionhandling ...................................................................................................................................................................................... - 281 -
27.1
cgigetsession() - Liefert eine Session-ID aus IP-Adresse und Systemzeit ................................................................................................................... - 281 -
27.2
setsessionident .............................................................................................................................................................................................................. - 281 -
27.3
delsession ...................................................................................................................................................................................................................... - 281 -
27.4
cgitestsession() - Testet eine Session-ID auf Gültigkeit ................................................................................................................................................ - 282 -
28.
CGI-Funktionen ................................................................................................................................................................................................................. - 282 -
28.1
cgibuffer() - Schaltet die CGI-Ausgabepufferung an oder aus ...................................................................................................................................... - 283 -
28.2
cgiclearbuffer() - Löscht den Inhalt des Ausgabepuffers ............................................................................................................................................... - 283 -
28.3
cgiclosebuffer() - Schreibt den Ausgabepuffer in die Standardausgabe und Beendet Pufferung................................................................................. - 284 -
28.4
cgiexec() - Führt externes Programm aus ..................................................................................................................................................................... - 285 -
28.4.1
cgiexec unter Windows .......................................................................................................................................................................................... - 285 -
28.4.2
cgiexec unter Linux ................................................................................................................................................................................................ - 286 -
28.5
cgigetparam() - Holt einen Parameter aus der Standardeingabe.................................................................................................................................. - 287 -
28.6
cgitestparam() - Prüft, ob ein Parameter mit der Methode "POST" übertragen wurde ................................................................................................. - 289 -
28.7
cgiwrite() - Gibt eine Zeichenkette an die Standardausgabe ........................................................................................................................................ - 290 -
28.8
cgiwritehtml() - Gibt eine Zeichenkette im HTML-Format an die Standardausgabe ..................................................................................................... - 290 -
28.9
cgiwriteln() - Gibt eine Zeichenkette + <Zeilenvorschub> an die Standardausgabe ..................................................................................................... - 291 -
28.10
cgiwritetemplate() - Schreibt den System-Ramtext in die Standardausgabe ............................................................................................................ - 291 -
28.11
endcgi() - Beendet CGI-Funktionalität ....................................................................................................................................................................... - 292 -
28.12
endsema() - Semaphore beenden ............................................................................................................................................................................. - 292 -
28.13
getquerystring() - Holt einen Wert aus der Environment-Variablen "QUERY_STRING" .......................................................................................... - 292 -
28.14
http_post() - RAMText per POST übertragen ............................................................................................................................................................ - 293 -
28.15
initcgi() - Initialisiert die CGI-Umgebung .................................................................................................................................................................... - 294 -
28.16
waitsema() - Setzt eine Sperre .................................................................................................................................................................................. - 294 -
29.
Datums/Uhrzeit-Funktionen ............................................................................................................................................................................................... - 295 -
29.1
Alle Datums- und Zeitfunktionen.................................................................................................................................................................................... - 295 -
29.2
datestr() - Datum als Zeichenkette ................................................................................................................................................................................ - 295 -
- 16 -
Inhaltsverzeichnis
29.3
datetime_to_unix() - Macht aus einer Datums- und Zeitangabe einen Unix-Timestamp .............................................................................................. - 296 -
29.4
day() - Extrahiert die Tageszahl aus einem Datum ....................................................................................................................................................... - 297 -
29.5
dayofweek() - Wochentag eines Datums....................................................................................................................................................................... - 297 -
29.6
monat() -Extrahiert den Monat aus einem Datum (veraltet) .......................................................................................................................................... - 298 -
29.7
month() - Monat aus einem Datum ................................................................................................................................................................................ - 299 -
29.8
now() – Systemzeit ........................................................................................................................................................................................................ - 299 -
29.9
str_to_unixtime() - Macht aus einem String einen Unix-Timestamp.............................................................................................................................. - 299 -
29.10
30.
timestr() - Zeitangabe als STRING ............................................................................................................................................................................ - 300 -
CodierungundVerschlüsselung .......................................................................................................................................................................................... - 302 -
30.1
RoutinenzurPasswort-Behandlung ................................................................................................................................................................................ - 302 -
30.2
Passwortabfrage über ein Anmeldeformular gegen einen Eintrag in einer TDB-Tabelle.............................................................................................. - 303 -
30.3
Passwortabfrage über ein Anmeldeformular gegen einen Eintrag in htaccess (Linux only) ......................................................................................... - 305 -
31.
Debugging.......................................................................................................................................................................................................................... - 307 -
31.1
Fehlerpositionen aufspüren ........................................................................................................................................................................................... - 307 -
31.2
Programmaufruf tracen .................................................................................................................................................................................................. - 307 -
31.3
Erweiterte Information in Log-Datei schreiben - setcgilog() .......................................................................................................................................... - 309 -
32.
ALLGEMEINE FEHLERCODES UND -MELDUNGEN ..................................................................................................................................................... - 311 -
32.1
Fehlercode 13: Index passt nicht zur Datei (16.07.2013) ............................................................................................................................................. - 311 -
32.2
Index defekt ................................................................................................................................................................................................................... - 312 -
32.3
Code 188: Unbekannter IO/Fehler (23.01.2012) ......................................................................................................................................................... - 312 -
32.4
Noch unbearbeitet ......................................................................................................................................................................................................... - 312 -
32.5
Compiler und Interpreterfehler – 10.10.2011................................................................................................................................................................. - 313 -
32.5.1
Var Name(VAR x) : STRING ................................................................................................................................................................................. - 320 -
32.5.2
TestSel(selection : STRING) : REAL ..................................................................................................................................................................... - 321 -
32.5.3
SetCGILog(msg : STRING) : REAL ....................................................................................................................................................................... - 321 -
32.5.4
SetFields(replacestr:STRING):REAL..................................................................................................................................................................... - 322 -
32.5.5
CGIBuffer(mode:REAL):REAL............................................................................................................................................................................... - 322 -
- 17 -
Inhaltsverzeichnis
32.6
Der SQL-Konflikt ............................................................................................................................................................................................................ - 323 -
32.6.1
Die Konfigurationsdatei tdbengine.ini .................................................................................................................................................................... - 323 -
32.6.2
Zu guter Letzt ......................................................................................................................................................................................................... - 323 -
32.6.3
VAL() ...................................................................................................................................................................................................................... - 324 -
33.
Neue Operatoren ............................................................................................................................................................................................................... - 326 -
34.
Neue Funktion InitArray ..................................................................................................................................................................................................... - 326 -
35.
Erweiterte Syntax ............................................................................................................................................................................................................... - 326 -
36.
Das System-Modul ............................................................................................................................................................................................................ - 327 -
37.
Die Markierung von Datensätzen ...................................................................................................................................................................................... - 328 -
37.1
38.
Markierungsliste auf der Platte zwischenspeichern – 17.01.2013 ................................................................................................................................ - 331 Grundsätzliche CGI-Frage: Was passiert, wenn der Browser die Anforderung abbricht? ................................................................................................ - 332 -
38.1.1
Verbessertes Handling bei abgebrochenen Verbindungen ................................................................................................................................... - 332 -
38.1.2
Erweitertes Zahlenformat für Zeitangaben ............................................................................................................................................................ - 333 -
38.1.3
Neue Funktion zum Löschen eines Index ............................................................................................................................................................. - 333 -
38.1.4
Umbenennung der Funktion PRIMFILE in PRIMTABE ......................................................................................................................................... - 333 -
38.2
DiewesentlichenNeuerungenderVersion6.25: ............................................................................................................................................................... - 334 -
38.2.1
Version für FreeBSD 4.x ........................................................................................................................................................................................ - 334 -
38.2.2
Aufhebung der 64-KByte-Grenze für EASY-Programme ...................................................................................................................................... - 335 -
38.2.3
Neue Funktion: DBRights ...................................................................................................................................................................................... - 335 -
38.3
Verbesserte Syntax und initialisierte Variablen ............................................................................................................................................................. - 337 -
38.4
Neue Datentypen ........................................................................................................................................................................................................... - 340 -
38.5
NeueunderweiterteFunktionen....................................................................................................................................................................................... - 341 -
38.5.1
MarkDoubles(db:INTEGER;index:INTEGER|STRING):INTEGER ........................................................................................................................ - 342 -
38.5.2
DelMarkedRecords(db:INTEGER):INTEGER ....................................................................................................................................................... - 342 -
38.5.2.1
Reset, Rewrite und TAppend ......................................................................................................................................................................... - 342 -
38.5.3
read(f:INTEGER[n:INTEGER|d:CHAR]):STRING ................................................................................................................................................. - 343 -
38.5.4
Verbesserungen .................................................................................................................................................................................................... - 343 -
- 18 -
Inhaltsverzeichnis
38.6
EinfacheSocket-Funktionen ........................................................................................................................................................................................... - 343 -
38.7
tdbengine als Unix-Scriptsprache .................................................................................................................................................................................. - 347 -
38.7.1
Weitere Verbesserungen ....................................................................................................................................................................................... - 349 -
38.7.2
Hinweise zur Fehlersuche ..................................................................................................................................................................................... - 349 -
38.7.3
Neue Funktionen zum ausprobieren (nurLinux-Version) ....................................................................................................................................... - 349 -
38.7.3.1
Timeout(sec:INTEGER):INTEGER ................................................................................................................................................................ - 350 -
38.7.3.2
Server(port:INTEGER;proc:Prozedur;maxconnections:INTEGER):INTEGER ............................................................................................. - 351 -
38.7.4
Erweiterungen von anderen Funktionen ................................................................................................................................................................ - 352 -
38.7.5
Korrekturen ............................................................................................................................................................................................................ - 352 -
38.7.5.1
38.7.6
38.8
NeueFunktionen .................................................................................................................................................................................................... - 353 -
38.7.6.1
CGITestParam(parameter:STRING):0|1 ....................................................................................................................................................... - 353 -
38.7.6.2
ExecProg(FileName:STRING):INTEGER ...................................................................................................................................................... - 353 -
Programminterne Kommunikation über Environment-Variablen ................................................................................................................................... - 354 38.8.1.1
39.
ToDBF(dat:INTEGER;fn_dbf:STRING;lastfield,ansi:INTEGER) ................................................................................................................... - 352 -
Cookies .......................................................................................................................................................................................................... - 356 -
Tdbengine.ini ..................................................................................................................................................................................................................... - 357 -
39.1
Default Werte ................................................................................................................................................................................................................. - 358 -
39.2
Aufrufe erlauben bzw. blockieren .................................................................................................................................................................................. - 358 -
39.2.1
Abschaltung der cgi-Aktivitäten - Stopcgi .............................................................................................................................................................. - 358 -
39.2.2
Weiterleitung im Falle stopcgi=1 - location ............................................................................................................................................................ - 359 -
39.3
Aufrufe protokollieren ..................................................................................................................................................................................................... - 359 -
39.3.1
Logfiles aktivieren – logcgi / log ............................................................................................................................................................................. - 360 -
39.3.2
Dateiname des Dateiname des Logfile festlegen log = ......................................................................................................................................... - 360 -
39.3.3
Logdatei für jedes Programm anlegen .................................................................................................................................................................. - 360 -
39.4
Cgibuf = CGI-Puffer definieren
................................................................................................................................................................................... - 361 -
39.5
Sema = Semaphoren für die Einrichtung von Schreibsperren ...................................................................................................................................... - 362 -
39.6
timeout ........................................................................................................................................................................................................................... - 363 - 19 -
Inhaltsverzeichnis
39.7
overrun ........................................................................................................................................................................................................................... - 363 -
39.8
Pfad zu Bibliotheken in der tdbengine.ini = Libpath (19.11.2011) ................................................................................................................................ - 364 -
39.9
buffer_idents= ................................................................................................................................................................................................................ - 365 -
39.10
Programmbezogene Eintrage .................................................................................................................................................................................... - 365 -
39.11
semadir=Verzeichnis für Semaphoren
................................................................................................................................................................... - 365 -
39.12
Direkt ausführbare Scripten ....................................................................................................................................................................................... - 366 -
39.12.1
cdmode .............................................................................................................................................................................................................. - 366 -
39.13
Beispiele zur TDBengine.ini....................................................................................................................................................................................... - 367 -
39.14
Lokale Dokumentationsdateien ................................................................................................................................................................................. - 367 -
40.
Wissenswertes zu Cookies ................................................................................................................................................................................................ - 369 -
41.
DasServer-Projekt ............................................................................................................................................................................................................. - 371 -
41.1
Dokumentation ............................................................................................................................................................................................................... - 372 -
41.1.1
Grundsätzliches ..................................................................................................................................................................................................... - 372 -
41.1.2
Installation .............................................................................................................................................................................................................. - 372 -
41.1.2.1
Apache ........................................................................................................................................................................................................... - 372 -
41.1.2.2
Manuelle Konfiguration .................................................................................................................................................................................. - 374 -
41.1.2.3
Internet Information Server (Windows) .......................................................................................................................................................... - 374 -
41.1.2.4
sambar (Windows) ......................................................................................................................................................................................... - 375 -
41.1.2.5
Testen ............................................................................................................................................................................................................ - 375 -
41.1.3
Variablentypen ....................................................................................................................................................................................................... - 375 -
41.1.3.1
Einfache Datentypen ..................................................................................................................................................................................... - 376 -
41.1.3.1.1
REAL ......................................................................................................................................................................................................... - 376 -
41.1.3.1.2
INTEGER ................................................................................................................................................................................................... - 376 -
41.1.3.1.3
STRING ..................................................................................................................................................................................................... - 376 -
41.1.3.1.4
CHAR ......................................................................................................................................................................................................... - 377 -
41.1.3.1.5
ARRAYS UND ARRAY-TYPEN ................................................................................................................................................................ - 377 -
- 20 -
Inhaltsverzeichnis
41.1.3.1.6
TBITS[] ....................................................................................................................................................................................................... - 377 -
41.1.3.1.7
INTEGER[] ................................................................................................................................................................................................. - 378 -
41.1.3.1.8
CHAR[]undBYTE[] ..................................................................................................................................................................................... - 378 -
41.1.4
41.1.4.1
TEXTDATEIEN ÖFFNEN .............................................................................................................................................................................. - 380 -
41.1.4.2
LESEN AUS TEXTDATEIEN ......................................................................................................................................................................... - 381 -
41.1.4.3
SCHREIBEN IN TEXTDATEIEN ................................................................................................................................................................... - 381 -
41.1.4.4
DATEIEN SCHLIEßEN .................................................................................................................................................................................. - 381 -
41.1.4.5
DATEIENDE ERMITTELN ............................................................................................................................................................................. - 381 -
41.1.4.6
DIE ARBEIT MIT TEMPLATES ..................................................................................................................................................................... - 382 -
41.1.4.7
TEMPLATE LADEN ....................................................................................................................................................................................... - 382 -
41.1.4.8
TEMPLATE AUSGEBEN ............................................................................................................................................................................... - 382 -
41.1.4.9
ERSTETZUNG AUSFÜHREN ....................................................................................................................................................................... - 382 -
41.1.4.10
KONFIGURATIONSDATEIEN ....................................................................................................................................................................... - 383 -
41.1.5
42.
Textdateien ............................................................................................................................................................................................................ - 379 -
Systemfunktionen .................................................................................................................................................................................................. - 385 -
41.1.5.1
ALLGEMEINES.............................................................................................................................................................................................. - 385 -
41.1.5.2
DAS AKTUELLE VERZEICHNIS ERMITTELN ............................................................................................................................................. - 386 -
41.1.5.3
ANLEGEN UND LÖSCHEN VON VERZEICHNISSEN ................................................................................................................................. - 386 -
41.1.5.4
VERZEICHNIS WECHSELN ......................................................................................................................................................................... - 386 -
41.1.5.5
SUCHE IN VERZEICHNISSEN ..................................................................................................................................................................... - 388 -
41.1.5.6
SYSTEMDATUM UND SYSTEMZEIT ........................................................................................................................................................... - 390 -
41.1.5.7
ALLGEMEINE DATEINFUNKTIONEN .......................................................................................................................................................... - 391 -
41.1.5.8
FREIEN SPEICHER IN PARTITION ERMITTELN - diskfree ........................................................................................................................ - 392 -
41.1.5.9
UMGEBUNGSVARIABLE ERMITTELN ........................................................................................................................................................ - 393 -
Das Semaphorenkonzept der tdbengine ........................................................................................................................................................................... - 394 - 21 -
Inhaltsverzeichnis
42.1
VIELE TDBENGINES GLEICHZEITIG .......................................................................................................................................................................... - 394 -
42.2
SYNCHRONISATION NOTWENDIG ............................................................................................................................................................................ - 394 -
42.3
...UND SCHLUCKT PERFORMANCE .......................................................................................................................................................................... - 395 -
42.4
SO MACHEN WIR ES RICHTIG ................................................................................................................................................................................... - 395 -
42.5
SEMAPHOREN IN DER KONFIGURATIONSDATEI .................................................................................................................................................... - 395 -
43.
42.5.1.1
SEMAPHOREN IM PROGRAMM .................................................................................................................................................................. - 397 -
42.5.1.2
LANGE AKTUALISIERUNGSLÄUFE ............................................................................................................................................................ - 398 -
TIPPS FÜR PHP-PROGRAMMIERER .............................................................................................................................................................................. - 400 43.1.1
43.1.1.1
BESCHREIBUNG .......................................................................................................................................................................................... - 401 -
43.1.1.2
INSTALLATION ............................................................................................................................................................................................. - 401 -
43.1.1.3
VERWENDUNG............................................................................................................................................................................................. - 401 -
43.1.1.4
TABELLENSPERREN ................................................................................................................................................................................... - 402 -
43.1.1.5
SATZ-SPERREN ........................................................................................................................................................................................... - 402 -
43.1.1.6
AUTOMATISCHE SPERREN ........................................................................................................................................................................ - 402 -
43.1.2
43.2
Locking Daemon .................................................................................................................................................................................................... - 401 -
Funktion Choice ..................................................................................................................................................................................................... - 403 -
43.1.2.1
Verwendung bei Auswahlfeldern ................................................................................................................................................................... - 404 -
43.1.2.2
Bedingter Funktionsaufruf .............................................................................................................................................................................. - 404 -
43.1.2.3
Bekannte Bugs:.............................................................................................................................................................................................. - 404 -
Lektion 6: Die Datenbank der tdbengine ....................................................................................................................................................................... - 405 -
43.2.1
Terminologie .......................................................................................................................................................................................................... - 405 -
43.2.2
Das ADL-System ................................................................................................................................................................................................... - 407 -
43.2.3
Indizes .................................................................................................................................................................................................................... - 408 -
43.2.4
Performance .......................................................................................................................................................................................................... - 410 -
43.2.5
Aktualität ................................................................................................................................................................................................................ - 410 -
43.2.6
Flexibilität ............................................................................................................................................................................................................... - 410 -
- 22 -
Inhaltsverzeichnis
43.2.7
Praxis ..................................................................................................................................................................................................................... - 410 -
43.2.8
Berechtigungen ...................................................................................................................................................................................................... - 411 -
43.2.9
Lesen von Datensätzen ......................................................................................................................................................................................... - 412 -
43.2.10
Feldzugriffe ........................................................................................................................................................................................................ - 413 -
43.2.11
Adressen-Suchmaschine ................................................................................................................................................................................... - 417 -
44.
44.1
45.
JavaScript Lösungen ......................................................................................................................................................................................................... - 422 Zeitverzögerter Aufruf .................................................................................................................................................................................................... - 422 Linux Informationen ........................................................................................................................................................................................................... - 423 -
45.1
Dateien unter Linux zippen ............................................................................................................................................................................................ - 423 -
45.2
Alle Dateien in Kleinbuchstaben wandeln ..................................................................................................................................................................... - 423 -
46.
Abbildungsverzeichnis ....................................................................................................................................................................................................... - 424 -
47.
Stichwortverzeichnis .......................................................................................................................................................................................................... - 425 -
48.
Noch zu prüfenden Funktionen - 18.06.2012 ................................................................................................................................................................... - 428 -
- 23 -
Betriebssystemfunktion
1.
Meeting 10.06.2013
Agenda:
1. UTF-8
a.
b.
c.
d.
e.
f.
Native Funktion zur Konvertierung UTF-8 auf Ansi und zurück
Sortierung
Suche nach Index
Suche über Volltext
Konvertierung Datenbestand einer Tabelle von ANSI nach UTF-8
Funktionen mit Parameter für Code
i. ReadMemo
ii. Copymemo
iii. Rewrite
iv. Append
v. Reset
g. Betroffene Funktionen
i. Length
ii. Pos
iii. Scan
2. tdbengine neue Version
a. Start der neuen Projekte mit neuer tdbengine ohne Abwärtskompatibilität
b. Neuer Dateiheader
c. Mehrere tdbengine Versionen auf einem Server
3. Diskussionsbedarf
a. Neuer Parameter bei WriteRec: Öffnet die Tabelle erst mit dem WriteRec zum Schreiben und schliesst diese wieder
b. Transaktionsmechanismen nativer integrieren
c. Array: Schneidet Einträge aus einem Array aus und legt diese in einem neuen Array ab.
getArray(Array1, Array2,0,10) - schneidet die ersten 9 Einträge aus dem Array1 aus und legt diese in das Array2 ab.
Das Array2 wird automatisch richtig initialisiert.
d. Bedingtes Compilieren
e. Access violation wegen out of memory eventuell. Speicherprüfung
f.
4. Log-Dateien
a. CGI-Log
- 24 -
Betriebssystemfunktion
5.
6.
7.
8.
i. Prozesse aus dem CGI-Log bzw. definiertem Log ausnehmen
b. Error.log
i. .ec 1 liefert trotzdem error.log Eintrag
ii. FindAndMark liefert bei falscher Anweisung keinen Fehlereintrag
iii. Wird auf eine DEF-Anweisung referenziert, welche nicht deklariert wurde, so bricht die tdbengine ohne jede Fehlermeldung ab.
Indexdateien
a. Indexname auf 8 Zeichen begrenzt
b. weitere Indexdateien ohne CIN-Datei
c. physikalische Indexgröße
d. Anzahl Indizes auf 15 begrent
Physikalische Dateiegröße der DAT-Dateien
Aktuelle Fehler
a. Str(123,4,2,““) füllt mit CHR(0) auf
b. Verschlüsselte Tabellen können nicht gelesen werden
Wunschliste
a. MarkPattern kann auch mit einem hierarischen Index umgehen
- 25 -
Betriebssystemfunktion
2.
Bekannte Bugs
2.1
FindAndMark und utime-Feld im Index
Achtung:
FindAndMark liefert unter bestimmten Umständen keinen Treffer.
FindAndMark versucht, anhand der Suchanweisung einen passenden Index zu finden.
z.B. ist folgender Index definiert:
id=$AutoID_Bewertung:4, $Typ:10, $CreatedOn:4
Beispiel:
die folgende Anweisung liefert 2 Treffer
$Typ="SDB",$AutoID_Bewertung=3639
hingegen liefert diese Anweisung keinen Treffer:
$AutoID_Bewertung=3639,$Typ="SDB"
Die Ursache ist ein Bug in der tdbengine.
Im 2. Fall erkennt er, dass ein passender Index vorhanden ist.
Im Index ist aber noch das Feld CreatedOn vom Typ utime definiert.
Der Fehler ist jetzt, dass ein Feld vom Typ uTime im Index vorhanden ist, und in der Suche das Feld nicht angesprochen wird.
Intern wird jetzt die Suchanweisung nicht richtig umgesetzt und es wird kein Treffer ermittelt.
Der Fehler ist in allen tdbengine Version enthalten.
Workaround:
Entweder für das utime Feld eine Selektion mitgeben oder dafür sorgen, dass FindAndmark keinen passenden Index findet
- 26 -
Betriebssystemfunktion
3.
CGI-Anwendungen ohne Apache-Konfiguration
In der Linux- und FreeBSD-Version gibt es nun die Möglichkeit, direkt ausführbare Scripten zu erzeugen. Dazu muss in der Haupt-Konfigurationsdatei
(tdbengine.ini im Programmverzeichnis) unter globals folgender Eintrag stehen:
[globals]
direct_scripting=1
interpreder=/var/www/cgi-bin/tdbengine
Im Anschluss daran ergänzt die tdbengine jedes von Ihr erzeugte Programm (prg-Datei) um die Zeile
#!/Pfad/zur/tdbengine
Als Pfad zur tdbengine verwendet die tdbengine
 den Pfad zu sich selbst, wenn nichts anderes angegeben ist
 den Eintrag unter globals.interpreter in der tdbengine.ini, wenn im Script kein anderer Pfad angegeben ist, oder
 den Pfad, der in der ersten Zeile der mod-Datei in der Form #!Pfad/zur/tdbengine steht
Ausserdem macht die tdbengine die erzeugten prg-Dateien ausführbar.
Das hat folgenden Vorteil: Die tdbengine kann nun auch auf solchen Servern eingesetzt werden, die keine Installation im HTTP-Server zulassen. Mit anderen
Worten: Auf jedem Linux- oder FreeBSD-Server, der eigene CGI's zulässt, kann die tdbengine ohne weitere Installation durch den Administrator verwendet
werden.
Damit kann die tdbengine bei allen Providern eingesetzt werden, die Ihnen Webspace mit freien CGI's zur Verfügung stellen ( Strato, 1und1, PureTec...).
Dazu gehen Sie so vor:
Kopieren Sie die tdbengine in Ihr CGI-Verzeichnis (bzw. ein Unterverzeichnis) , beispielsweise /cgi-bin/tdbengine.
Testen Sie, ob die tdbengine ausgeführt wird, indem Sie folgende URL aufrufen: http://www.ihre_domain.de/cgi-bin/tdbengine/tdbengine.
Kopieren Sie die mitgelieferten Tools (pdk, dmk etc.) in das selbe Verzeichnis wie die tdbengine.
Starten Sie das Programm-Entwicklungstool mit http://www.ihre_domain.de/cgi-bin/tdbengine/pdk.prg.
Es ist keine Konfiguration des Web-Servers notwendig! Die Sache ist sehr einfach:
Legen Sie unterhalb Ihres CGI-Verzeichnisses ein Verzeichnis "tdbengine" an.
Kopieren Sie in dieses Verzeichnis alle Dateien der tdbengine
- 27 -
Betriebssystemfunktion
Sorgen Sie dafür, dass folgende Dateien ausführbar sind: tdbengine
Das war's auch schon. Wenn Ihre Domain beispielsweise "www.meinedomain.de" heisst ind Ihr CGI-Verzeichnis "cgi-bin", dann sollte jetzt der Aufruf von
"http://www.meinedomain.de/cgi-bin/tdbengine/test.prg" das bekannte "Hello world!" in die Browser-Anzeige zaubern.
Hier noch ein Hinweis für Windows-Anwender: Das einfache Kopieren der Datein mittels FTP reicht nicht aus, da in den meisten Fällen derart transpotierte
Dateien für alle anderen Anwender ausser dem User selbst nur lesbar sind. Aber nahezu alle bekannten FTP-Klienten können die Zugriffrechte ändern.
- 28 -
Betriebssystemfunktion
4.
Rechte für Apache User (Linux only)
Linux unterscheidet streng zwischen den persönlichen Berechtigungen eines Benutzers und die des Webservers (Apache).
Der Apache User hat zwar standardmäßig das Recht, die vom Benutzer hochgeladenen Dateien anzusehen, kann die Dateien aber nicht verändern. Auch das
Hinzufügen von Dateien ist ihm untersagt. Da die tdbengine als Modul von Apache ausgeführt wird, wird es auch mit dessen Rechten ausgeführt.
Damit die tdbengine Programme aber richtig laufen können, werden mind. die folgenden Einstellungen benötigt.
Hinweis:
Wird über ein Prg ein Verzeichnis erstellt oder Dateien geschrieben, so müssen die Rechte entsprechend erweitert werden.
4.1
Verzeichnis, welches die tdbengine beinhaltet
Verzeichnis
tdbengine
tdbengine.ini
4.2
0700
0700
0600
Database (bzw. sysbase)
Database benötigt als Verzeichnis 0700, für die einzelnen Dateien innerhalb von /database reicht 0600.
Das Recht 0700 für database wird benötigt, da der baseman Unterverzeichnisse sowohl in /database als auch in /syswork anlegt.
4.3
Program (Verzeichnis mit den prg-Dateien)
Der Verzeichnis benötigt 0700, die Programme ( *.prg) benötigen kein Ausführenrecht, es reicht daher 0600.
Das gilt auch, wenn innerhalb einer Procedure cgiexec aufgerufen wird.
Wird jedoch mit direct scripting gearbeitet, so sind das Ausführenrecht erforderlich.
- 29 -
Betriebssystemfunktion
Hinweis:
Wird über ein prg ein Verzeichnis angelegt, so hat das neue Verzeichnis i.d.R. das Recht 0775.
Wird dies nicht gewünscht, so wird empfohlen anstelle der tdbengine Funktion mkdir die Neuanlage eines Verzeichnisses über die console (cgiexec)
durchzuführen.
- 30 -
Betriebssystemfunktion
5.
Tabelle anlegen
Eine Tabelle wird aufgrund einer Strukturdefinition generiert.
Diese Strukturdefinition muss als Datei physikalisch vorliegen und erhält üblicherweise die Extension .def und liegt im Verzeichnis /def
Sie gliedert sich in zwei Bereiche
 [Struktur]
 [Index]
5.1
5.1.1
Tabellendefinition Abschnitt [Structure]
Beispiel für Adressdatenbank:
[STRUCTURE]
field_1=KonzernID,STRING,20
field_2=Name,STRING,50
field_3=Vorname,STRING,50
field_4=Telefon,STRING,30
field_5=TelefonPrivat,STRING,30
field_6=HandyPrivat,STRING,30
field_7=Handy,STRING,30
field_8=Anrede,SELECT,Herr,Frau
field_9=PersonenSucheinrichtung,STRING,10
field_10=eMail,STRING,100
field_11=StammNummer,NUMBER,4
field_12=Fachkundeerhalt,BOOLEAN
field_13=VerfallFachkunde,DATE
field_14=AnzahlFachkundeTage,NUMBER,1
field_15=LastFachkundeTag,DATE
field_16=Aktiv,BOOLEAN
field_17=addFachkunde,BOOLEAN
field_18=Stammpersonal,BOOLEAN
field_19=Bereitschaftspersonal,BOOLEAN
field_20=VerfuegbarVon,DATE
field_21=VerfuegbarBis,DATE
field_22=UserName,STRING,20
field_23=PW,STRING,20
field_24=Reihenfolge,NUMBER,4
- 31 -
Betriebssystemfunktion
field_25=AutoID_Funktion,LINK,funktion.dat
field_26=AutoID_Ersatzfunktion,LINK,funktion
field_27=AutoID_Ersatzfunktion2,LINK,funktion
field_28=AutoID_OE,LINK,gruppen.dat
field_29=AutoID_OE_Urlaubspezifisch,LINK,gruppen.dat,u
field_30=Admin,BOOLEAN
field_31=Vorzimmer,BOOLEAN
field_32=Planer,BOOLEAN
field_33=EinsatzplanGenehmiger,BOOLEAN
field_34=EinsatzplanPerMail,BOOLEAN
field_35=UrlaubsplanNotPublic,BOOLEAN
field_36=Genehmiger,BOOLEAN
field_37=DelGenehmigteAbwesenheit,BOOLEAN
field_38=AutoID_Vertritt,LINK,mitarbeiter.dat
field_39=AutoID_Vertritt2,LINK,mitarbeiter.dat
field_40=AutoID_Vertritt3,LINK,mitarbeiter.dat
field_41=AutoID_Schwerpunkt,LINK,schwerpunkt.dat
field_42=RechtMitarbeiter,BOOLEAN
field_43=RechtFachkunde,BOOLEAN
field_44=RechtAusbildung,BOOLEAN
field_45=RechtRevision,BOOLEAN
field_46=RechtSetup,BOOLEAN
field_47=RechtArchiv,BOOLEAN
field_48=RechtGenehmigt,BOOLEAN
field_49=CheckPassword,BOOLEAN
field_50=FremdKey,STRING,40
field_51=SelectUrlaubsplan,BLOB
field_52=ReadNews,UTIME
field_53=AutoID,AUTO
field_54=Signatur,MEMO
field_55=Hauptverantwortlicher,BOOLEAN
field_56=Urlaubsplan,BOOLEAN
field_57=Qualifikation,STRING,30
field_58=Farbe,STRING,7
field_59=CreatedBy,STRING,25
field_60=CreatedOn,UTIME
field_61=ChangedBy,STRING,25
field_62=ChangedOn,UTIME
[INDEX]
inr=$AutoID
id=$UserName
- 32 -
Betriebssystemfunktion
index_1=mitarbe1.ind:$KonzernID
index_2=mitarbe2.ind:$Name
index_3=mitarbe3.ind:$Vorname
index_4=mitarbe4.ind:$Aktiv
index_5=mitarbe5.ind:$FremdKey
index_6=mitarbe6.ind:$StammNummer
[FULLTEXT]
fulltext=JA
ExtABC=0123456789/Fields=complete:3
Step=1000000
Hinweis:
Wer mit dem baseman arbeitet kann auch Kommentare und Leerzeilen in der Definitionsdatei einfügen. Die Nummerierung ist dann auch nicht mehr erforderlich.
Damit ist die Struktur besser lesbarer und wichtige Informationen stehen am richtigem Ort.
Dann sieht die Definitionsdatei z.B. so aus:
[STRUCTURE]
KonzernID,STRING,20
Name,STRING,50
Vorname,STRING,50
Telefon,STRING,30
TelefonPrivat,STRING,30
HandyPrivat,STRING,30
Handy,STRING,30
Anrede,SELECT,Herr,Frau
PersonenSucheinrichtung,STRING,10
eMail,STRING,100
StammNummer,NUMBER,4
Fachkundeerhalt,BOOLEAN
VerfallFachkunde,DATE
AnzahlFachkundeTage,NUMBER,1
LastFachkundeTag,DATE
Aktiv,BOOLEAN
..* addFachkunde=true => alle Einsätze des Mitarbeiters zählen immer als Fachkunde, wenn auch FUNKTION.addFachkunde=true
addFachkunde,BOOLEAN
Stammpersonal,BOOLEAN
Bereitschaftspersonal,BOOLEAN
VerfuegbarVon,DATE
VerfuegbarBis,DATE
UserName,STRING,20
- 33 -
Betriebssystemfunktion
PW,STRING,20
Reihenfolge,NUMBER,4
AutoID_Funktion,LINK,funktion.dat
AutoID_Ersatzfunktion,LINK,funktion
AutoID_Ersatzfunktion2,LINK,funktion
AutoID_OE,LINK,gruppen.dat
AutoID_OE_Urlaubspezifisch,LINK,gruppen.dat,u
.. Rechte
Admin,BOOLEAN
Vorzimmer,BOOLEAN
Planer,BOOLEAN
EinsatzplanGenehmiger,BOOLEAN
EinsatzplanPerMail,BOOLEAN
UrlaubsplanNotPublic,BOOLEAN
.. Vertreter-Zustimmung durch Vorgesetzte erforderlich
Genehmiger,BOOLEAN
DelGenehmigteAbwesenheit,BOOLEAN
..Vertretungsregelung AutoID_Vertritt ist der Vertreter von $AutoID
AutoID_Vertritt,LINK,mitarbeiter.dat
AutoID_Vertritt2,LINK,mitarbeiter.dat
AutoID_Vertritt3,LINK,mitarbeiter.dat
AutoID_Schwerpunkt,LINK,schwerpunkt.dat
RechtMitarbeiter,BOOLEAN
RechtFachkunde,BOOLEAN
RechtAusbildung,BOOLEAN
RechtRevision,BOOLEAN
RechtSetup,BOOLEAN
RechtArchiv,BOOLEAN
RechtGenehmigt,BOOLEAN
CheckPassword,BOOLEAN
FremdKey,STRING,40
.. \Rechte
SelectUrlaubsplan,BLOB
ReadNews,UTIME
- 34 -
Betriebssystemfunktion
AutoID,AUTO
Signatur,MEMO
..nicht mehr erforderlich
Hauptverantwortlicher,BOOLEAN
Urlaubsplan,BOOLEAN
Qualifikation,STRING,30
.. die folgenden Felder sind überflüssig und werden entfallen
.. die Zuordnung zu einer Gruppe erfolgt in mit2gruppe.dat
Farbe,STRING,7
CreatedBy,STRING,25
CreatedOn,UTIME
ChangedBy,STRING,25
ChangedOn,UTIME
[INDEx]
inr=$AutoID
id=$UserName
index_1=mitarbe1.ind:$KonzernID
index_2=mitarbe2.ind:$Name
index_3=mitarbe3.ind:$Vorname
index_4=mitarbe4.ind:$Aktiv
index_5=mitarbe5.ind:$FremdKey
index_6=mitarbe6.ind:$StammNummer
[FULLTEXT]
fulltext=JA
ExtABC=0123456789/Fields=complete:3
Step=1000000
- 35 -
Betriebssystemfunktion
5.1.2
Beispiel für eine Medikamentendatei (z.B. mit vielen Select Feldern)
[STRUCTURE]
Generic,STRING,19
AutoID_Applikation,LINK,application
.. die Infusionsdauer wird normalerweise in h und min angegeben
Infusion_Tage,NUMBER,4
Infusion_Stunden,NUMBER,4
Infusion_Minuten,NUMBER,4
Einheit,STRING,10
Loesungsmittel,LINK,loesung.dat
AutoID_Elimination,LINK,elimination
vorwiegend_renal,BOOLEAN
vorwiegend_biliaer,BOOLEAN
.. Toxizität
Knochenmark,SELECT,keine,wenig,maessig,stark
Emesis,SELECT,keine,wenig,maessig,stark
Diarrhoe,SELECT,keine,wenig,maessig,stark
Obstipation,SELECT,keine,wenig,maessig,stark
Alopezie,SELECT,keine,wenig,maessig,stark
Leber,SELECT,keine,wenig,maessig,stark
Niere,SELECT,keine,wenig,maessig,stark
Herz,SELECT,keine,wenig,maessig,stark
Nerven,SELECT,keine,wenig,maessig,stark
Haut,SELECT,keine,wenig,maessig,stark
Allergie,SELECT,keine,wenig,maessig,stark
Sonstige_Nebenwirkung,STRING,100
Besonderheit,STRING,60
Cave,STRING,100
AutoID,AUTO
[INDEX]
inr=$AutoID
id=$1
- 36 -
Betriebssystemfunktion
5.1.3
Beispiel für die Verwendung eines Blob-Feldes
[STRUCTURE]
..Typ=0: Stammpersonalverteilung
..Typ=1: Urlaubsgruppen
..Typ=2: Vertretergruppen
..Typ=3: Personalhierarchie
..Typ=4: Bereitschaften
..Typ=5: Gruppen, die gemeinsam Einsicht in den Urlaubsplan haben
Typ,NUMBER,1
Anzahl,NUMBER,4
Bezeichnung,STRING,100
AutoID_Owner,LINK,mitarbeiter.dat
AutoID_Archiv,LINK,m2g_archiv
AutoID_Gruppe,LINK,gruppen.dat
AutoID_Funktionsgruppe,LINK,fkt_gruppe,u
Mitglieder,BLOB
minAnwesend,NUMBER,1
AutoID,AUTO
[INDEx]
inr=$AutoID
id=$Typ,$AutoID_Archiv
index_1=vertr_1.ind:$AutoID_Archiv,$AutoID_Gruppe
Das ,u bei einem Linkfeld bedeutet, dass über dieses Feld kein Index angelegt wird.
AutoID_Funktionsgruppe,LINK,fkt_gruppe,u
- 37 -
Betriebssystemfunktion
5.2
Tabellendefinition Abschnitt [Index]
5.1
Tabelle anlegen - MakeDB
MakeDB(Tabellenname, Paßwort, Verschlüsselungscode, Strukturdefinition [,Quelle]) : Fehlercode
Die Strukturdefinition ist der Name einer Textdatei, die folgendermaßen aufgebaut ist:
[STRUCTURE]
field_1=fieldspec
field_2=fieldspec
...
[INDEX]
id=indexdef
index_1=ind-name:ind-desc
index_2=ind-name:ind-desc
...
Der Abschnitt unter [INDEX] ist nur erforderlich, wenn die Tabelle automatisch numeriert wird.
Eine Feldspezifikation fieldspec hat folgende Struktur:
Feldbezeichner,Typdefinition[,(Importfeld)]
Als Feldbezeichner sind alle Bezeichner zugelassen, die mit einem Buchstaben oder Unterstrich beginnen und nur aus Buchstaben, Ziffern und Unterstrich
bestehen. Die maximale Länge beträgt 35 Zeichen.
Folgende Typdefinitionen sind zugelassen:
Der Abschnitt unter [INDEX] ist nur erforderlich, wenn die Tabelle automatisch numeriert wird.
Eine Feldspezifikation fieldspec hat folgende Struktur:
Feldbezeichner,Typdefinition[,(Importfeld)]
Als Feldbezeichner sind alle Bezeichner zugelassen, die mit einem Buchstaben oder Unterstrich beginnen und nur aus Buchstaben, Ziffern und Unterstrich
bestehen.
Die maximale Länge beträgt 35 Zeichen.
Folgende Typdefinitionen sind zugelassen:
Zeichenketten:
- 38 -
Betriebssystemfunktion
STRING,Länge (Länge von 1 bis 255)
-> Stringfeld mit der angegeben Maximallänge
Zahlen:
NUMBER,NumCode[,Nachkommastellen][,U]
-> numerisches Feld je nach NumCode
Num
1
2
4*
Code Typ
BYTE
16-Bit INTEGER
32-Bit INTEGER
Wertbereich
Ganze Zahlen VON 0 BIS 255
Ganze Zahlen VON - 32768 BIS + 32767
Ganze Zahlen - 2147483648 BIS + 21474836476
*) nur tdbengine, nicht kompatibel zur VDP oder TurboDatenbank
Wird der Parameter U angegeben, so wird in Selektion zwischen dem Wert 0 und "undefiniert" unterschieden.
BOOLEAN
-> Ein Bit (true/false)
Memos und Blobs:
MEMO
-> ergibt ein Memofeld
Der Inhalt eines Memofeldes ist ein Verweis auf eine zusätzliche Memodatei. In Memodateien werden auschließlich unformatierte Texte gespeichert.
BLOB [,blocksize]
-> ergibt ein Blobfeld
Der Inhalt eines Blobfeldes ist ein Verweis auf eine zusätzliche Blobdatei In Blobdateien werden beliebige Daten gespeichert. Mit blocksize kann die Größe der
Cluster in der Blobdatei festgelegt werden (Minimum = 64, Maximum = 8192, Default = 4096). Pro Eintrag in die Blobdatei wird im Schnitt ein halber Cluster
verschwendet, aber zu kleine Cluster verlängern die Zugriffszeiten.
AUTO-INCREMENT:
AUTO[,Startnummer]
-> Autonummern-Feld (AUTO INCREMENT)
- 39 -
Betriebssystemfunktion
Die Zählung beginnt (in einer neuen Tabelle) mit der Startnummer (bzw 1, falls keine angegeben wurde).
Datum und Zeit:
DATE
-> Datumsfeld (dd.mm.yyyy)
TIME
-> Zeitfeld (hh:mm)
UTIME
-> Unix-Timestamp (dd.mm.yyyy_hh:mm:ss)
Aufzählung:
SELECT,Wert1,Wert2,Wert3,...
-> Auswahlfeld mit dem angegebenen Wertbereich
Verknüfungen:
REL
-> Relationsfeld zwischen den angegebenen Tabellen
Achtung: Die tdbengine erzeugt nicht automatsich die zugehörige Relationstabelle. Hierfür steht die Funktion GenRel zur Verfügung.
LINK,Tabelle
-> ADL-Linkfeld zur angegebenen Tabelle
Es wird nicht gepüft, ob die Tabelle existiert oder ob die automatisch numeriert wird.
Importfelder festlegen:
Ist in GenTable eine Quelle angegeben, so wird das spezifizierte Feld durch den Inhalt des angegebenen Importfeldes der Quelle ersetzt.
Ist kein Importfeld angegeben, so wird der spezifizierte Feldname verwendet.
- 40 -
Betriebssystemfunktion
Wird als Quelle der gleiche Dateiname wie unter Ziel angegeben, so wird die Tabelle entsprechend restrukturiert. Als Abkürzung für den gleichen Dateinamen ist
das Zeichen "@" erlaubt.
Hinweis: Die Strukturdefinition ist eigentlich eine Maschinendatei, die von einem Hilfsprogramm durch Interaktion mit dem Anwender erzeugt wird. Wenn Sie
eine Strukturdefinition von Hand erstellen, sollten Sie unbedingt folgendes beachten:
Die Numerierung der Felder muß lückenlos sein.
Keine Feldnummer darf doppelt vorkommen.
Fügen Sie neue Felder immer am Ende an, da die Reihenfolge nicht wesentlich ist.
Wenn Sie im Zuge einer Umstrukturierung einen Feldbezeichner ändern wollen, so vergessen Sie nicht, den bisherigen Bezeichner als Importfeld
anzugeben, da andernfalls der Feldinhalt verloren ist.
Die Umstrukturierung einer Tabelle ist immer ein kritischer Vorgang, wenn die Tabelle bereits Informationen enthält. Sie können zur Datensicherheit beitragen,
indem Sie auf die direkte Umstrukturierung verzichten, und statt dessen die bisherige Tabelle mit RenTable umbenennen und die umbenannte Tabelle als Quelle
angeben. Falls irgendetwas schiefgeht, können Sie immer noch auf die bisherige Tabelle zugreifen.
Syntax
MAKEDB(db_p, db_pw : STRING; c : INTEGER; def : STRING[; db_i : INTEGER]) : INTEGER
Parameter
db_p : Pfad zur Hauptdatei (.dat) der Tabelle
db_pw : Passwort der Tabelle
db_c : Verschlüsselungscode der Tabelle
def : Pfad zu einer Strukturdefinition
db_i : Pfad zu einer Import-Tabelle
Ergebnis
0 : Tabelle wurde erfolgreich erzeugt
sonst : Fehlercode
5.1.1.1 Zeichenkette
STRING,Länge (Länge von 1 bis 255)
Stringfeld mit der angegeben Maximallänge
- 41 -
Betriebssystemfunktion
5.1.1.2 Zahlen
NUMBER,NumCode[,Nachkommastellen][,U]
numerisches Feld je nach NumCode
NumCode
Typ
Wertbereich
1
Byte
Ganze Zahlen von 0 bis 255
2
16-Bit-Integer
Ganze Zahlen von - 32768 bis + 32767
4*
32-Bit Integer
Ganze Zahlen von - 2147483648 bis + 2147483647
6**
6-Byte REAL (altes Borland-Format)
Fließkommazahlen von -2.9 x 10^39 bis +1.7 x 10^38 bei 11-12 sign. Stellen
8***
8-Byte-REAL Fließkommazahlen (ANSI
double)
Fließkommazahlen von -5.0 x 10^324 bis +1.7 x 10^308 bei 15-16 sign. Stellen
*) nur tdbengine, nicht kompatibel zur VDP oder TurboDatenbank
**) kompatibel zu allen VDP- und TurboDatenbank-Versionen
***) nur tdbengine und VDP (ab 2.5)
Nachkommstellen werden nur bei den REAL-Typen berücksichtig. Sie sind wesentlich für den Datentransfer mit den Funktionen GetField und SetField sowie
beim Export in andere Datenbankformate. Intern werden alle REAL-Typen immer mir der optimalen Genauigkeit gespeichert.
Wird der Parameter U angegeben, so wird in Selektion zwischen dem Wert 0 und "undefiniert" unterschieden.
5.1.1.3 Memos und Blobs
MEMO
ergibt ein Memofeld
Der Inhalt eines Memofeldes ist ein Verweis auf eine zusätzliche Memodatei. In Memodateien werden auschließlich unformatierte Texte gespeichert.
BLOB [,blocksize]
ergibt ein Blobfeld
Der Inhalt eines Blobfeldes ist ein Verweis auf eine zusätzliche Blobdatei In Blobdateien werden beliebige Daten gespeichert. Mit blocksize kann die Größe der
Cluster in der Blobdatei festgelegt werden (Minimum = 64, Maximum = 8192, Default = 4096). Pro Eintrag in die Blobdatei wird im Schnitt ein halber Cluster
verschwendet, aber zu kleine Cluster verlängern die Zugriffszeiten.
- 42 -
Betriebssystemfunktion
5.1.1.4 AUTO-INCREMENT
AUTO[,Startnummer]
Autonummern-Feld (AUTO INCREMENT)
Die Zählung beginnt (in einer neuen Tabelle) mit der Startnummer (bzw 1, falls keine angegeben wurde)
5.1.1.5 Datum und Zeit
DATE
Datumsfeld (dd.mm.yyyy)
TIME
Zeitfeld (hh:mm)
UTIME
UNIX-Timestamp (Sekunden seit 1.1.1970)
5.1.1.6 Aufzählung
SELECT,Wert1,Wert2,Wert3,...
Auswahlfeld mit dem angegebenen Wertbereich
5.1.1.7 Verknüpfungen
REL
Relationsfeld zwischen den angegebenen Tabellen
Achtung: Die tdbengine erzeugt nicht automatsich die zugehörige Relationstabelle. Hierfür steht die Funktion GenRel zur Verfügung.
LINK,Tabelle
ADL-Linkfeld zur angegebenen Tabelle
Es wird nicht gepüft, ob die Tabelle existiert oder ob die automatisch numeriert wird.
5.1.1.8 IMPORTFELDER FESTLEGEN
Ist in MakeDB() eine Quelle angegeben, so wird das spezifizierte Feld durch den Inhalt des angegebenen Importfeldes der Quelle ersetzt. Ist kein Importfeld
angegeben, so wird der spezifizierte Feldname verwendet.
Wird als Quelle der gleiche Dateiname wie unter Ziel angegeben, so wird die Tabelle entsprechend restrukturiert. Als Abkürzung für den gleichen Dateinamen ist
das Zeichen "@" erlaubt.
- 43 -
Betriebssystemfunktion
Hinweis: Die Strukturdefinition ist eigentlich eine Maschinendatei, die von einem Hilfsprogramm durch Interaktion mit dem Anwender erzeugt wird. Wenn Sie
eine Strukturdefinition von Hand erstellen, sollten Sie unbedingt folgendes beachten:
Die Numerierung der Felder muß lückenlos sein.
Keine Feldnummer darf doppelt vorkommen.
Fügen Sie neue Felder immer am Ende an, da die Reihenfolge nicht wesentlich ist.
Wenn Sie im Zuge einer Umstrukturierung einen Feldbezeichner ändern wollen, so vergessen Sie nicht, den bisherigen Bezeichner als Importfeld anzugeben, da
andernfalls der Feldinhalt verloren ist.
Die Umstrukturierung einer Tabelle ist immer ein kritischer Vorgang, wenn die Tabelle bereits Informationen enthält. Sie können zur Datensicherheit beitragen,
indem Sie auf die direkte Umstrukturierung verzichten, und statt dessen die bisherige Tabelle mit RenTable() umbenennen und die umbenannte Tabelle als
Quelle angeben. Falls irgendetwas schiefgeht, können Sie immer noch auf die bisherige Tabelle zugreifen.
- 44 -
Betriebssystemfunktion
5.2
Öffnen und Schliessen von Datenbanken
5.2.1
opendb() - Öffnen einer Tabelle
Beschreibung
Öffnet eine TDB-Datenbank
Syntax
OPENDB(path : STRING [;password : STRING [; code : INTEGER [; rights : INTEGER]]]) : INTEGER
Parameter
path : Pfad zur Datei, die die Tabelle enthält
password : Paßwort der Tabelle
code : Verschlüsselungscode der Tabelle
rights : Zugriffsrechte (Summe aus):
1 : neue Datensätze anfügen
2 : bestehende Datensätze überschreiben
4 : Datensätze löschen
8 : Indizes anlegen und löschen
15 : zum Lesen und Schreiben öffnen
256 : Virtuell
Ergebnis
Tabellenhandle, wenn > 0
0 : Fehler
Modus ist ein Addition der einzelner Grundmodi, mit denen die gewünschte Zugriffsart gekennzeichnet wird:
0 : (Vorgabe) Es dürfen keine Veränderungen an der Tabelle vorgenommen werden.
1 : Es dürfen neue Datensätze erzeugt werden
2 : Es dürfen bestehende Datensätze überschrieben werden
4 : Es dürfen Datensätze gelöscht werden
8 : Es dürfen Indizes erzeugt und gelöscht werden
Die Parameter können zu einem gewünschten Ergebnis addiert werden.
Z.B. 3 für Datensätze anfügen und bestehende überschreiben, nicht aber löschen.
Der Modus 15 erlaubt somit sämtliche Modifikationen an der Tabelle.
- 45 -
Betriebssystemfunktion
Hinweis: Tritt beim Öffnen der Tabelle ein Fehler auf, so wird das Programm normalerweise mit einer entsprechenden Fehlermeldung beendet. Wenn die interne
Fehlerbehandlung abgeschaltet wurde, ist der Rückgabewert der Funktion 0, und der Fehler kann mit TDB_ErrorCode ermittelt werden.
VAR d : INTEGER
.EC 1
IF d:=OpenDB("database/adressen.dat")=0 THEN
CGIWriteHTML("Beim Öffnen der Tabelle ist folgender Fehler aufgetreten: ")
CGIWriteHTML(TDB_ErrorStr(TDB_LastError))
ELSE
CGIWriteHTML(Str(FileSize(d))+" Datensätze stehen zu Ihrer Verfügung")
END
Anders als die TurboDatenbank öffnet die tdbengine über L- oder R-Felder verknüpfte Tabellen nicht automatisch, sondern diese müssen bei Bedarf separat mit
OpenDB() geöffnet werden. Dabei ist zu beachten, daß immer die zuletzt geöffnete Tabelle in die bisherige Relationsstruktur eingebaut wird. Das bedeutet
beispielsweise, daß Relationstabellen immer erst nach den Tabellen, zwischen denen sie die Verknüpfung definiert, geöffnet werden darf. Wird eine Tabelle mit
einem L-Feld geöffnet, muß die zugehörige Link-Tabelle schon geöffnet sein, damit die Verknüpfung aktiv wird.
Wenn kein Zugriff auf die angegebene Tabelle möglich ist, wird ein Laufzeitfehler ausgelöst. In diesem Fall sollte immer die Fehlerursache (TDB_LastError)
untersucht werden.
Die geöffnete Tabelle wird zur Primärtabelle.
Warnung:
Tabellen sollten nicht ausserhalb von Procedure zum Schreiben geöffnet werden.
Hier kann es dazu führen, dass dadurch die Semaphore nicht freigegeben wird, obwohl der Prozess an sich beendet ist.
Aufgefallen ist dies, weil in einem anschließenden Programm diese Semaphore nicht mehr gesetzt werden kann, obwohl eigentlich der vorherige Prozess schon
beendet war.
Beispiel 1: OpenDB + ReOpenDB + CloseDB
VAR db : INTEGER
IF db:=OpenDB('database/adressen.dat','',0,0) THEN //read access
..
ReOpenDB(db,15) //enable write access
..
CloseDB(db)
- 46 -
Betriebssystemfunktion
END
- 47 -
Betriebssystemfunktion
5.2.1.1 Tabelle virtuell öffnen – opendb im Modus 271
Benötigt man temporär eine Ergebnistabelle, so kann man die Daten zum Beispiel in ein Array schreiben.
Eleganter ist es natürlich, die Daten wiederum in einer TDB-Tabelle zu haben.
Dies erleichtert den Zugriff deutlich und man kann alle Funktionen benutzen, welche für den Tabellenzugriff existieren.
Zum Beispiel ist die Suche in einem Array deutlich schwieriger und langsamer als die Suche in einer TDB-Tabelle.
Die Tabelle darf aber wiederum nicht physikalisch beschrieben werden, da es sonst zu Zugriffskonflikten kommt, wenn mehrere Prozesse gleichzeitig ihre
Ergebnisse in die Tabelle beschreiben wollen.
In diesem Fall würde man ein Datenchaos erhalten.
Die Lösung hierfür sind sogenannte virtuelle Tabellen..
Legen Sie hierzu wie gewohnt eine TDB-Tabelle in der gewünschten Struktur an.
Die Tabelle sollte leer sein, ansonsten beinhaltet die virtuelle Tabelle bereits den Datenstand der Ausgangstabelle.
Kleiner Tipp:
Wenn man zu debug-Zwecken den Modus von 271 auf 15 ändert, kann man den Inhalt der Tabelle einsehen und so das Ergebnis prüfen.
Allerdings sind dann bereits Daten in der Tabelle. Diese müssen natürlich wieder gelöscht werden, da ansonsten der bereits vorhanden Inhalt immer zu den
neuen Datensätzen hinzukommt.
Dies kann zu Überraschungen im Ergebnis führen
Öffnen Sie die Tabelle mit dem Parameter 271.
A := OpenDB(„../database/temp.dat“,““,0,271)
Nun können Sie in die Tabelle schreiben und auch wie gewohnt Abfragen vornehmen.
Wenn man die Ergebnistabelle abspeichern möchte, so kann man bei closeDB einen Zielpfad hinterlegen.
Wir möchten z.B. unsere temp.dat zum Schluss in ein Verzeichnis /daten ablegen.
Dazu geben wir an:
closeDB(A,“../daten“)
Existiert die Tabelle aber bereits in dem Verzeichnis, so wird unter Umständen die Tabelle nicht überschrieben.
Daher sollte man vorher die bereits vorhandenen Dateien im Zielverzeichnis löschen.
Dies kann man auch mit der Funktion delDB machen, das delDB auch alle Indexdateien gleich mitlöscht.
- 48 -
Betriebssystemfunktion
delDB(„../daten/temp.dat“)
5.2.2
closedb() - Schliesst eine Tabelle
Beschreibung
Am Programmende werden alle Tabellen automatisch geschlossen. Dennoch belegen geöffnete Tabellen Systemressourcen wie Speicher und Filehandles und
sollten demnach bei Nichtgebrauch wieder geschlossen werden.
Hinweis: Alle schreibenden Operationen auf eine Tabelle werden von tdbengine nicht gepuffert. Die Veränderungen stehen damit unmittelbar nach der Operation
allen Netzwerkteilnehmern zur Verfügung.
Eine Tabelle wird mit dem Befehl CloseDB wieder geschlossen.
Als Parameter wird die Variable erwartet, welcher OpenDB zugewiesen wurde.
Syntax
closeDB (tabellenhandle : INTEGER [;dir : STRING) : INTEGER
Parameter
tabellenhandle : von OPENDB
dir : Wenn angegeben und es sich um eine virtuelle Tabelle handelt, dann werden die zur Tabelle gehörenden Dateien (Tabelle selbst,
Indizes, Memos und Blobs) in dieses Verzeichnis kopiert.
Ergebnis
Fehlercode :0 = erfolgreich geschlossen
Regelfall:
VAR A : Integer
A:= opendb(„../database/adressen.dat“)
…
CloseDB(A)
Beispiel mit erneutem Öffnen der Tabelle zum Schreiben
VAR db : INTEGER
IF db:=OpenDB('database/adressen.dat','',0,0) THEN //read access
..
ReOpenDB(db,15) //enable write access
- 49 -
Betriebssystemfunktion
..
CloseDB(db)
END
5.3
reopendb() - Öffnet eine bereits geöffnete Tabelle mit anderen Rechten
Beschreibung
Es gilt ja die Regel, dass man eineTabelle nur mit den unbedingt benötigten Rechten öffnen sollte. Manchmal kommt es aber vor, dass man eine Tabelle zum
Lesen geöffnet hat, und nun will man in dieseTabelle schreiben. Also CloseDB mit anschließendem OpenDB? Diese Lösung hat den Nachteil, dass nach dem
CloseDB der alte Tabellenhandle nicht mehr gültig ist und der neu (nachOpenDB) nicht mehr mit dem alten übereinstimmen muss. Um das zu verhindern gibt es
nun die Funktion ReOpenDB, das denTabellenhandle bewahrt und die Tabelle mit neuen Rechten wieder öffnet.
ReOpenDB() wechselt den Zugriffsmodus für eine bereits geöffnete Tabelle. Somit kann z.B. eine bereits zum Lesen geöffnete Tabelle auch mit Schreibrechten
geöffnet werden, ohne dass sie zuvor explizit mit CloseDB() hätte geschlossen werden müssen.
Das Tabellen-Handle bleibt bestehen - bei Benutzung von CloseDB wäre das nicht zu garantieren gewesen.
Syntax
ReOpenDB(db : INTEGER; accessmode : INTEGER) : INTEGER
Parameter
db : von OpenDB oder ReOpenDB erzeugtes Tabellenhandle
accessmode : Tabellenzugriffsmodus (siehe OpenDB)
Ergebnis
0 falls die Tabelle nicht in einen anderen Zugriffsmodus versetzt werden konnte
Beispiel 1: OpenDB + ReOpenDB + CloseDB
VAR db : INTEGER
IF db:=OpenDB('database/adressen.dat','',0,0) THEN //read access
..
ReOpenDB(db,15) //enable write access
..
CloseDB(db)
END
5.4
setalias() - Tabelle mehrfach öffnen
Beschreibung
- 50 -
Betriebssystemfunktion
Erzeugt einen weiteren Handle für eine bereits geöffnete Tabelle. Die Tabelle kann in Subreports etc. unter dem Alias-Namen angesprochen werden. Die AliasTabelle hat einen eigenen Satzpuffer, eine eigene Markierungsliste und eine eigene Zugriffsverwaltung.
Wenn man innerhalb einer Datei auf einen Datensatz steht und gleichzeitig in der gleichen Datei einen anderen Datensatz öffnen möchte, so verstellt sich der
Dateizeiger.
Aus diesem Grund kann man eine Tabelle mehrfach öffnen.
Hierzu gibt es den Befehl setAlias
Öffnen Sie zuerst wie gewohnt die gewünschte Tabelle z.B. address.dat.
Anschließend bekommen Sie zum Tabellenhandle A2 die Tabelle address.dat erneut geöffnet.
Var A : Integer = OpenDB("../database/address.dat","",0,15)
SetAlias(A,"a2.dat")
Nun kann man mit ReadRec auf unterschiedliche Datensätze der gleichen Tabelle zugreifen und zum Beispiel die Einträge zweier Datensätze vergleichen.
readRec(A,findauto(A,4711))
readRec(A2,findauto(A2,1234))
if getfield(A,“Name“)=getfield(A2,“Name“)
…
5.5
Informationen zu einer geöffneten Datenbank auslesen
5.5.1
dbno() - Liefert das TableHandle
Syntax
DBNo(DBName : STRING) : INTEGER
Parameter
DBName: Der Name muss wie von DBName(db : INTEGER) ermittelt übergeben werden, also mit '.dat' oder '.rel', aber ohne Pfad.
Ergebnis
Liefert das TableHandle von DBName oder 0, wenn nicht gefunden.
- 51 -
Betriebssystemfunktion
5.5.2
dbrights() - Rechte an geöffneter Tabelle
Beschreibung
Mit DBRights() können die aktuellen Rechte an der Tabelle db überprüft werden.
Hinweis: Diese Funktion löst FILEMODE() ab.
Syntax
DBRIGHTS(db : INTEGER ) : INTEGER
Parameter
db : Tabellenhandle von OpenDB
Ergebnis
Rechte, die mit OpenDB oder ReOpenDB gesetzt wurden
Beispiel 1: B_And + DBRights.Prüfung der Tabellenrechte
IF DBRights(db) B_And 2 =0 THEN
CgiWriteLn('No write permission on '+DBName(db))
ELSE
WriteRec(db,RecNo(db))
END
5.5.3
dbdir() - Verzeichnisteil des Pfades einer Tabelle
Beschreibung
Der gesamte Pfad einer Tabelle kann so ermittelt werden:
DBDIR(db)+DBNAME(db)
Syntax
DBDIR(db : INTEGER) : STRING
Parameter
db : Tabellenhandle von OPENDB
Ergebnis
Verzeichnisteil des Pfades einer Tabelle, also Verzeichnis einschließlich abschließendem Schrägstrich
- 52 -
Betriebssystemfunktion
5.5.4
dbname() - Namensteil des Pfades einer Tabelle
Syntax
DBNAME(db : INTEGER) : STRING
Parameter
db : Tabellenhandle von OPENDB
Ergebnis
Namensteil des Pfades Beschreibung es gilt
Pfad : DBDIR(db)+DBNAME(db)
5.5.5
getstructure() - Struktur eines Tabellen-Feldes ermitteln
Syntax
GETSTRUCTURE(db : INTEGER; field : INTEGER|STRING) : STRING
Parameter
db : Tabellenhandle von OpenDB
field : Feldnummer oder Feldbezeichner (als STRING)
Ergebnis
Feldstruktur gemäß Tabellenstruktur. Näheres siehe MAKEDB
Beispiel 1:
GetStructure
Überprüfen ob Notes ein Memo-Feld ist
VAR db : INTEGER
...
db:=OpenDB('database/addresses.dat')
IF GetStructure(db,'Notes') like '*,MEMO*' THEN
...
StrukturdesangegebenFeldesin Form: Label,Typ,Spezifikation
FolgendeTypensindmöglich:
- 53 -
Betriebssystemfunktion
Typ
Spezifikation
Bemerkung
STRING
1..255
Maximallänge des Strings
NUMBER
1
Byte
2
16-Bit-Integer
4
32-Bit-Integer
6,n
Fließkomma mit (default) n Nachlommastellen (altes Format aus Kompatibilitätsgründen)
8,n
Fließkomma mit (default) n Nachkommastellen: double precision
...,U
Zusatzspezifikation für alle numerischen Typen zur Unterscheidung: 0 und keine Eingabe
MEMO
Text beliebiger Länge
BLOB
Binäre Daten
AUTO
AUTO-Increment
DATE
Datum
TIME
Zeit
SELECT
(Konstante_1,Konstante_2...)
Auswahl (Aufzählung)
LINK
Tabellenname
ADL-Verknüpfung 1:n
REL
ADL-Verknüpfung n:m
- 54 -
Betriebssystemfunktion
5.5.6
gettype() - Feldtyp ermitteln (veraltet)
Beschreibung
Wurde durch GETSTRUCTURE ersetzt.
Syntax
GETTYPE(db : INTEGER; field : INTEGER | STRING[; length : INTEGER]) : STRING
Parameter
db : Tabellenhandle von OpenDB
field : Feldnummer oder Feldbezeichner (als String)
length: Länge der Ausgabe
Ergebnis
Typ des Feldes als String:
S : STRING
C : CHAR
B : BYTE (= NUMBER,1)
I : INTEGER (= NUMBER,2)
F : FLOAT (=NUMBER,8)
D : DATE
J : BOOL
M : MEMO
A : ENUM
N : AUTOINCREMENT
L : (ADL-)LINK
P : BLOB
R : (ADL-)RELATION
Z : TIME
- 55 -
Betriebssystemfunktion
5.5.7
filesize() - Anzahl der Datensätze einer Tabelle
Beschreibung
FILESIZE liefert nicht die physikalische Dateigröße der Tabelle, sondern die aktuelle Anzahl der Datensätze (Zeilen).
Zur Bestimmung der pysikalischen Dateigröße gibt es die Funktion GETSIZE, die allerdings einen Pfad als Parameter benötigt.
Wenn Tabellen mit GENLIST und GENREL angelegt wurden und in eine Volltextindizierung (im Minimalmodus) eingezogen wurden, liefert FILESIZE für diese
Tabellen kein Ergebnis.
Syntax
FILESIZE(Tabellenhandle[,Modus] : INTEGER) : INTEGER
Parameter
Tabellenhandle (von OPENDB)
Modus = 0 (Vorgabe) Standard - Anzahl der Datensätze in der zugehörigen .dat (bzw. -1 bei Volltext-Stichwort-Listen).
Modus =1 Erweitert
 Anzahl der ID-Einträge im ID-Index bei Stichwortlisten, die mittels ScanRecs und Modus=4 - erzeugt wurden.
 Anzahl der Einträge im IN2-Index bei REL-Dateien, die mittels ScanRecs im Modus=4 erzeugt wurden.
Ergebnis
Aktuelle Anzahl der Datensätze in der Tabelle.
5.5.8
getmode() - Tabellenrechte ermitteln (veraltet)
Beschreibung
Wird duch DBRIGHTS ersetzt.
Syntax
GETMODE(db : INTEGER) : STRING
Parameter
db : Tabellenhandle von OpenDB
Ergebnis
String aus den Zeichen
'N' : neue Datensätze verboten
'E' : Datensätze überschreiben verboten
'L' : Datensätze löschen verboten
- 56 -
Betriebssystemfunktion
'I' : Indizierung verboten
5.5.9
filemode() - Liefert die Zugriffsrechte an einer Tabelle (veraltet)
Beschreibung
Diese Funktion wurde durch DBRIGHTS ersetzt, die den numerischen Zugriffsmodus liefert.
Syntax
FILEMODE(db : INTEGER) : STRING
Parameter
db : Tabellenhandle von OpenDB
Ergebnis
String aus folgenden Zeichen:
N : Anhängen verboten (New)
E : Überschreiben verboten (Edit)
L : Löschen verboten (deLete)
I : Indexfunktionen verboten (Index)
5.6
Allgemeine Datenbankfunktionen
5.6.1
primtable() - Setzt die Primärtabelle
Beschreibung
PRIMTABLE initialisert den Relationsbaum einer Datenbank.
Die alte Funktion primfile wird dadurch ersetzt.
Syntax
PRIMTABLE(tabelle : INTEGER) : INTEGER
Parameter
tabelle : Handle von OPENDB
Ergebnis
tabelle, wenn alles okay, sonst 0
- 57 -
Betriebssystemfunktion
5.6.2
relation()
Syntax
RELATION
- 58 -
Betriebssystemfunktion
5.6.3
Tabellenstruktur ermitteln
GetDef(db : REAL; structfile : STRING) : REAL
db : Tabellenhandle von OpenDB()
structdef : Pfad zu einer Textdatei, in die die Strukturdefinition geschrieben wird.
Rückgabewert: 0 = ok, sonst Fehlercode
Die Tabellenstruktur der geöffneten Tabelle db wird in die angegebene Datei geschrieben. Diese kann dann wiederum zum Erzeugen neuer Tabellen verwendet
werden. Als Ziel sind auch Ramtexte zulässig.
Leere Kopie einer geöffneten Tabelle db erzeugen
GetDef(db,'ramtext:text:structure')
MakeDB('kopie_von'+DBName(db),'',0,'ramtext:text:structure')
5.6.4
getpw() - Passwort einer Tabelle ermitteln
Syntax
GETPW(db : INTEGER) : STRING
Parameter
db : Tabellenhandle von OpenDB
Ergebnis
Passwort der Tabelle
5.6.5
getcode() - Verschlüsselungscode einer Tabelle
Beschreibung
Die Funktion wrid beispielsweise eingesetzt, wenn eine Tabelle exportiert werden soll, wobei Passwort und Verschlüselungscode der Ursprungstabelle erhalten
werden.
Syntax
GETCODE(db : INTEGER) : INTEGER
- 59 -
Betriebssystemfunktion
Parameter
db : Tabellenhandle von OpenDB
Ergebnis
Verschlüsselungscode der Tabelle
0, wenn db nicht verschlüsselt
- 60 -
Betriebssystemfunktion
5.6.6
deldb() - Löscht eine Tabelle inklusive aller dazugehörenden Elemente.
Beschreibung
Enfernt die Tabelle inklusive aller zugehörigen Elemente wie MEMO, BLOB und Indizes.
Syntax
DELDB(db_path : STRING; password : STRING; code : INTEGER) : INTEGER
Parameter
db_path : Pfad der Tabelle password : Passwort (default : "")
code : Verschlüsselungscode (default : 0)
Ergebnis
Fehlercode 0 : erfolgreich gelöscht
5.6.7
rendb() - Gibt einer Tabelle einen neuen Dateinamen
Beschreibung
Eine Tabelle sollte niemals mit RENAME umbenannt werden. Sie könnte dann in den meisten Fällen nicht mehr geöffnet werden, weil beispielsweise MEMOoder BLOB-Dateien von der Namensänderung nicht mitbekommen. Noch schwerwiegender ist die fehlernde Umbenennungen der Indizes.
Rendb benennt nicht nur die Tabelle, sondern auch Memo, Blob und Indexdateien bei Bedarf um.
Syntax
RENDB(path, password : STRING; code : INTEGER; newname : STRING) : INTEGER
Parameter
path : Pfad zur Tabelle
password : Passwort zum Öffnen der Tabelle
code : Verschlüsselungscode
newname : neuer Tabellenname (ohne Verzeichnis)
Ergebnis
Fehlercode,
0 : Tabelle wurde umbenannt
- 61 -
Betriebssystemfunktion
5.6.8
cleardat() - Löscht alle Datensätze einer Datei
Beschreibung
Löscht ohne weitere Rückfrage sämtliche Datensätze aus der Tabelle.
Dabei wird einfach der Tabellenheader neu geschrieben und damit die Anzahl Datensätze auf Null gesetzt.
Ein anschließendes RegenAll zur Auffrischung der Indexdateien ist nicht erforderlich.
Die AutoID des Datensatzes wird wieder auf Null zurückgesetzt, so dass der nächste neu geschriebene Datensatz mit der AutoID=1 beginnt.
Ein SetAuto(FileNr,1) kann damit entfallen.
Mit Cleardat erhält man eine leere Datei und korrekte Indexeinträge.
Eventuell kann man dann mit setauto() die Autonummer wieder zurücksetzen, wenn eine ganz jungfräuliche Tabelle erwartet wird.
Damit ClearDat() greifen kann muss die Tabelle zum Lesen geöffnet sein.
Syntax
ClearDat(Tabelle: INTEGER) : INTEGER
Parameter
Tabelle: Tabellenhandle von OPENDB
Ergebnis
Fehlercode
0 : Tabelle erfolgreich geleert
Beispiel
VAR fc : INTEGER
fc:=ClearDat(KFZ)
IF fc=115 THEN cgiwriteln('Die Tabelle ist nur zum Lesen geoeffnet')
ELSIF fc<>0 THEN
cgiwriteln(TDB_ERRORSTR(fc))
ELSE
cgiwriteln('Die Tabelle ist jetzt leer')
END
Interner Vermerk am 06.12.2011
Inwieweit auch Memo und Blob-Dateien korrekt zurückgesetzt werden muss noch geprüft werden.
Es kommt auch immer wieder vor, dass Indexdateien noch die ursprüngliche Größe haben. Auch das ist noch ungeklärt.
- 62 -
Betriebssystemfunktion
6.
Datensätze lesen, schreiben und löschen
Die tdbengine stellt für jede Tabelle einen Datensatzpuffer zur Verfügung. Alle Datenfeld-Funktionen beziehen sich auf diesen Datensatzpuffer.
Wann immer es möglich ist (also eine Autonummer in der Tabelle enthalten ist), sollte die Information über einen Datensatz als Autonummer zwischen CGIProgrammen erfolgen.
Eine andere Problematik steckt in der Verwendung von Volltext-Indizes, die ja auch auf physikalische Satznummern zurückgreifen. Ein Volltextindex wird nicht
automatisch aktualisiert, wenn ein Datensatz gelöscht wird. Das ist auch gar nicht möglich, da Volltextindizes kein Bestandteil der Tabelle sind, die tdbengine
demnach auch keine Information darüber haben kann.
Ein Ausweg besteht darin, einen Volltextindex nach dem Löschen in einer Tabelle neu zu erzeugen. Bei kleiner Tabellen ist diese Vorgehensweise aufgrund der
hohen Geschwindigkeit der tdbengine auch durchaus praktizierbar. Der andere, aufwendigere und auch elegantere Ausweg besteht darin, den Volltextindex
dynamisch mit der Tabelle mitzuführen. Mit ScanRec() und UnScanRec() stehen dazu die benötigten Funktionen zur Verfügung.
6.1
ReadRec(Tabellennummer,Satznummer) : Integer
Liest den Datensatz mit der Satznummer in den Datensatzpuffer der Tabelle mit der angegebenen Tabellennummer. Falls Satznummer=0, wird ein leerer
Datensatz bereitgestellt. Das Funktionergebnis ist die Satznummer, wenn der Datensatz gelesen werden konnte, andernfalls 0.
Bei einer illegalen Satznummer wird ein Laufzeitfehler ausgelöst.
6.2
WriteRec(Tabellenummer,Satznummer) : Integer
Schreibt den aktuelen Datensatzpuffer an der abgegeben Satznummer in die Tabelle. Zulässige Satznummern sind 1..FileSize(Tabellennummer)+1.
Illegale Satznummern führen zu einem Laufzeitfehler.
Liegt die Satznummer zwischen 1 und FileSize(Tabellennummer) so wird der entsprechende Datensatz überschrieben, andernfalls wird die Tabelle um einen
Datensatz erweitert.
WARNUNG: (11.10.2013)
Readrec(db,0)
Writerec(db,x)
Führt zu einem Eintrag mit der AutoID=0, falls x zu einem bereits vorhandenen Datensatz gehört.
- 63 -
Betriebssystemfunktion
6.3
DelRec(Tabellennummer,Satznummer) : Integer
Löscht den Datensatz mit der angegebenen Satznummer. Die tdbengine geht beim Löschen eines Datensatzes so vor:
Der Datensatz wird aus sämtlichen Indizes der Tabelle gelöscht.
Handelt es sich um den letzen Datensatz der Tabelle, so wird die Tabelle um einen Eintrag verkürzt.
Andernfalls wird der letzte Eintrag der Tabelle an die Position des zu löschenden Datensatzes kopiert, alle Indizes entsprechend geändert und schließlich die
Tabelle um einen Eintrag verkürzt.
Dieses Vorgehen hat zwar den Vorteil, daß die Tabelle immer nur aktuelle Datensätze enthält. Vorsicht ist jedoch geboten, wenn sich Funktionen auf
physikalische Satznummern beziehen. Zwar liefert RecNo() immer die aktuelle Satznummer (auch wenn sich diese infolge Löschungen verschoben haben). Bei
CGI-Programmen ist jedoch darauf zu achten, daß die Verbindung immer wieder abgebrochen wird, und die Informationen über CGI-Variable an den nächsten
Aufruf übergeben wird. Hier kann der Bezug auf physikalische Satznummern fatale Folgen haben, denn die kann sich von einem Aufruf zum nächsten bereits
geändert haben.
Abhile schafft der Bezug auf die Autonummern der Tabelle. Dazu stellt die tdbengine zwei neue Funktionen zur Verfügung, mit der die Umwandlung von
Satznummern und Autonummern stark vereinfacht wird: AutoField() und FindAuto().
6.4
AutoField(Tabellennummer) : Integer
liefert die Nummer des AUTO-Feldes (bzw. 0, wenn nicht automatisch numeriert).
GetRField(Tabellenummer,AutoField(Tabellennummer)) liefert so die Autonummer eines Satzes der Tabelle.
Dafür gibt es die Funktion
6.5
AutoRecNo(db : Integer) : Integer
db : Tabellenhandle von OpenDB()
Rückgabewert:
RecNo(db) wenn kein AUTO-INCREMENT-Feld in db
GetRField(db,AutoField(db)) sonst
FindAuto(db,x : Integer) : Integer
- 64 -
Betriebssystemfunktion
db : Tabellenhandle von OpenDB()
x : (Auto-)Nummer
Rückgabewert:
x wenn kein AUTO-INCREMENT-Feld in db
FindRec(db,Str(x),0,1) sonst
6.6
Datensatz kopieren – 19.10.2012
Achtung, das kopieren eines Datensatzes darf keinesfalls durch folgende Anweisung erfolgen:
Readrec(A,4711)
Writerec(A,filesize(A)+1)
Das führt zu Dateninkonsistenz.
Die Memoeinträge und Blobeinträge sind anschließend korrupt.
Ebenfalls verboten ist die folgende Konstruktion.
Var iBuffer:Byte[]
Readrec(A,4711)
Getrec(A,iBuffer)
Readrec(A,.0)
Writerec(A,filesize(A)+1)
Putrec(A,iBuffer)
Writerec(A,recnr(A))
Durch das Putrec wird auch die Auto-Nummer des Quelldatensatzes übernommen und plötzlich hat man doppelte Autonummern.
Das ist das schlimmste, was passieren kann, da dadurch die eindeutige Referenz auf einen Datensatz nicht mehr stimmt.
Ein Datensatz wird immer wie folgt kopiert:
If readRec(E,id)
SetRecord(E,E)
WriteRec(E,FileSize(E)+1)
End
- 65 -
Betriebssystemfunktion
Wenn man dabei einen Feldinhalt aus dem alten Datensazt mitnehmen möchte, dann so:
If readRec(E,id)
GetRec(E,iBuffer)
SetRecord(E,E)
SetField(E,"Text","Kopie von "+GetField(E,"Text",iBuffer))
WriteRec(E,FileSize(E)+1)
End
7.
Datensatz suchen
7.1
FindRec
Sucht einen Datensatz über einen existierenden Index. Diese Art der Suche ist sehr schnell.
FindRec(db : INTEGER; cSuchStr : STRING; [Index]; [bMode : Integer]) : INTEGER
db : Tabellenhandle von OpenDB()
cSuchStr ist ein der Definition des Index entsprechender Suchstring. Ein Index, der z.B. so definiert ist "$Name, $Vorname", erwartet eine Suchanfrage wie
diese: "Muster, Max".
Klammern sie die einzelnen Suchbegriffe, wenn in den Teilstrings auch Kommas "," enthalten sein können: z.B. "(Muster),(Max, Jr)"
Index ist optional, und erwartet entweder die Indexnummer, oder den Indexnamen. Wenn der Parameter nicht übergeben wurde, wird der aktuell im Zugriff
befindliche Index verwandt. Sollte im letzteren Fall kein Index verwendet werden, sondern der Zugriff entweder auf "Markierung" oder auf "Nummer" stehen, dann
wird ein "Illegaler Zugriff"-Fehler ausgelöst.
bMode ist ebenfalls optional, allerdings nur dann zu setzen, wenn auch Index explizit angegeben wurde. Dieser Parameter bestimmt, ob die Suche nur einen
absoluten Treffer akzeptiert, oder einfach den "erstbesten" Treffer zurückliefert. bMode = 1 erfordert also eine 100%ige Übereinstimmung, damit FindRec() eine
Satznummer zurückliefert, bMode = 0 liefert ggfs. den nächstfolgenden Datensatz (in der Reihenfolge von Index) zurück, sucht somit also quasi nach ">=
cSearchStr".
Das Ergebnis der Funktion ist 0, wenn die Suche mit bMode = 1 durchgeführt und kein Datensatz gefunden wurde, ansonsten die
Satznummer des ersten Treffers.
7.2
Suche über hierarchischen Index
- 66 -
Betriebssystemfunktion
Existiert ein hierarchischer Index z.B. über $Name,$Vorname so kann wie folgt danach gesucht werden:
Readrec(A,findrec(A,“Götz,Hermann“,”Indexname”,1))
Das Komma im Suchbegriff wird als Trennzeichen interprediert.
Ist innerhalb des Suchbegriffes ein weiteres Trennzeichen, so wird der richtige Datensatz nicht mehr gefunden.
Beispiel:
Readrec(A,findrec(A,“Gö,tz,Hermann“,”Indexname”,1))
Hier wird dann nach Nachname=”Gö” und Vorname = “tz” gesucht.
Man sieht, dass dies eine völlig andere Suchanweisung ist.
Wenn man nicht sicher ist, dass innerhalb eines Suchbegriffes ein Komma enhalten ist, so sollte man den Suchbegriff klammern.
Readrec(A,findrec(A,“(Gö,tz),(Hermann)“,”Indexname”,1))
In diesem Fall wird wieder korrekt nach Nachname =”Götz” und Vorname=”Hermann” gesucht.
7.3
FirstRec
FirstRec() liefert die Satznummer des ersten Datensatzes zurück. Dabei wird die aktuellen Sortierung (siehe Access()) berücksichtigt.
FirstRec(db : INTEGER) : INTEGER
LastRec
LastRec() gibt die Satznummer des letzten Datensatzes nach der aktuell eingestellten Sortierreihenfolge zurück.
FirstRec(db : INTEGER) : INTEGER
7.4
NextRec
Ermittelt die physikalische Satznummer des, relativ zum derzeit gelesenen, nächsten Datensatzes. Die zugewiesene Sortierreihenfolge ist entscheidend.
NextRec(db : INTEGER) : INTEGER
- 67 -
Betriebssystemfunktion
7.5
PrevRec
Funktioniert wie NextRec(), nur in die andere Richtung. Ermittelt die Satznummer des, relativ zum derzeit eingelesenen, vorhergehenden Datensatzes, unter
Berücksichtigung der gewählten Sortierreihenfolge.
PrevRec(db : INTEGER) : INTEGER
Die Funktionen FirstRec() und NextRec() werden häufig innerhalb von Schleifen angewandt, um mehrere Datensätze der Reihe nach zu lesen.
PROCEDUREMain
VAR db, nRec : INTEGER
db := OpenDB("manyrow.dat")
Access(db, "mr_name.ind")
nRec := FirstRec(db)
WHILE nRec > 0 DO
ReadRec(db, nRec)
... // mache irgendwas damit, z.B. Ausgabe
nRec := NextRec(db)
END
ENDPROC
- 68 -
Betriebssystemfunktion
8.
Datensätze in einer Schleife auslesen – 02.12.2011
Es gibt drei verschiedene Möglichkeiten, Daten in einer Schleife auszulesen.
8.1
Sub / Endsub
Sub und endsub stammen noch aus der alten DOS-TDB und bietet eine einfache Möglichkeit an, Datensätze auszulesen.
In der DOS_TDB war zum Zeitpunkt des Kompilierens die Tabelle geöffnet und deswegen konnte man so arbeiten:
SUB $ADRESSEN.Name wie „A*“
$Name
ENDSUB
Hier wurde alle Name auf die Console ausgegeben, welche die Bedingung Name wie „A*“ erfüllen.
Bei der tdbengine sind die Tabellen zum Zeitpunkt des Kompilierens aber nicht geschlossen und daher würde obige Anweisung auf einen Fehler laufen.
Dies kann man umgehen, indem man den Kompiler anweisst, die Information nach dem SUB erst zur Laufzeit zu interpredieren.
Dies geschieht mit dem vorangestelltem Unterstrich:
VAR A : Integer = Opendb(„../database/adressen.dat“)
SUB _DBNAME(ADRESSEN)+‘,$Name wie „A*“‘
Write(0, getfield(ADRESSEN,“Name“))
ENDSUB
Übrigens kann man eine SUB-Schleife mit exit abbrechen:
VAR A : Integer = Opendb(„../database/adressen.dat“)
VAR z : Integer
SUB _DBNAME(ADRESSEN)+‘,$Name wie „A*“‘
Write(0, getfield(ADRESSEN,“Name“))
If z++ > 100 then
exit
end
ENDSUB
Hier wird die Schleife nach 100 Einträgen abgebrochen.
- 69 -
Betriebssystemfunktion
Hinweis:
Bei Verwendung von SUB / ENDSUB sollte die Primärtabelle vorher gesetzt werden.
Ist eine andere Tabelle als Primärtabelle gesetzt, so ergibt sich ein völlig anderes Ergebnis.
In diesem Fall werden die Einträge noch zusätzlich gefiltert.
Dann werden nur die Datensätze ausgeben, welche über eine Linkfeld mit einem Datensatz aus der Primärtabelle verbunden sind.
Hier greift also ein Automatismus, der durchaus auf gewünschte Ergebnisse liefert.
Damit die tdbengine weiß, welche Beziehung zwischen der Primärtabelle und der Tabelle besteht, über die der SUB laufen soll, muss nach dem Öffnen der
Tabellen die Anweisung Relation aufgerufen werden.
Relation speichert sich im Hintergrund die Beziehungen zwischen den geöffneten Tabellen.
Dabei wird so vorgegangen.
Es wird die erste geöffnete Tabelle gelesen.
Ist in der Tabelle ein Linkfeld, so wird intern folgende Beziehung aufgebaut.
TABELLE_A[Linkfeld]=TABELLE_B[AutoID]
Anschließend wird die Tabelle B gelesen und nach Linkfelder durchsucht.
Ist die Tabelle B nicht geöffnet, so wird dieser Eintrag übersprungen.
Bei jedem Linkfeld wird ein weiterer Eintrag in die interne Beziehungstabelle vorgenommen.
Zum Schluss ergibt sind eine Liste von Relationsbeziehungen.
Zum Beispiel:
GERAET[Besitzer] = ADRESSEN[AutoID]
GERAET[Vorbesitzer] = ADRESSEN[AutoID]
ADRESSEN[Land] = LAND[Laendereintrag]
Wenn man ein SUB-Report über die Tabelle GERAET macht und in einem geschachtelten SUB-Report auf die Adressen zugreift, so wird immer automatisch der
erste gangbare Pfad hergenommen.
Dieser wäre in unserem Beispiel
GERAET[Besitzer] = ADRESSEN[AutoID]
Und nicht
GERAET[Vorbesitzer] = ADRESSEN[AutoID]
- 70 -
Betriebssystemfunktion
Ein Programm hierzu könnte so aussehen
VAR GERAET
: Integer = Opendb(„../database/geraet.dat“)
VAR ADRESSEN : Integer = Opendb(„../database/adressen.dat“)
relation
SUB _DBNAME(GERAET)
cgiwriteln(Getfield(GERAET,“Inventurnummer“))
SUB _DBNAME(ADRESSEN)
cgiwriteln(„Besitzer : „+Getfield(ADRESSEN,“Name“))
ENDSUB
ENDSUB
Will man alle Adressen mit angekoppelten Geräten ausgeben, so sieht das so aus:
VAR ADRESSEN : Integer = Opendb(„../database/adressen.dat“)
VAR GERAET
: Integer = Opendb(„../database/geraet.dat“)
Relation
SUB _DBNAME(ADRESSEN)
cgiwriteln(„Besitzer : „+Getfield(ADRESSEN,“Name“))
SUB _DBNAME(GERAET)
cgiwriteln(Getfield(GERAET,“Inventurnummer“))
ENDSUB
ENDSUB
- 71 -
Betriebssystemfunktion
8.2
Firstrec / Nextrec
Die Methode, eine Tabelle mit FirstRec / NextRec zu durchlaufen ist vielen Programmierern schon eher geläufig.
Hier wollen wir das gleiche Ergebnis wie im Beispiel zu SUB erzielen, allerdings FirstRec, NextRec verwenden.
VAR ADRESSEN : Integer = Opendb("../database/adressen.dat")
VAR GERAET
: Integer = Opendb("../database/geraet.dat")
Var r,i : Integer
r := firstrec(A)
while readrec(ADRESSEN,r)
cgiwriteln("Besitzer : "+Getfield(ADRESSEN,"Name"))
r := NextRec(ADRESSEN)
access(GERAET,"$Besitzer"); setfilter(GERAET,getfield(ADRESSEN,"AutoID") ,getfield(ADRESSEN,"AutoID"),1)
i := FirstRec(GERAET)
While ReadRec(GERAET,i)
CGIWriteLn(GetField(GERAET,"Inventurnummer"))
End
end
wie viel einfacher es mit SUB geht zeigt dieses Beispiel:
VAR ADRESSEN : Integer = Opendb("../database/adressen.dat")
VAR GERAET
: Integer = Opendb("../database/geraet.dat")
Relation
Var r,i : Integer
r := firstrec(A)
while readrec(ADRESSEN,r)
cgiwriteln("Besitzer : "+Getfield(ADRESSEN,"Name"))
r := NextRec(ADRESSEN)
Sub _DBNAME(GERAET)
CGIWriteLn(GetField(GERAET,"Inventurnummer"))
EndSub
End
Hier wird aber wieder der Aufruf Relation benötigt.
- 72 -
Betriebssystemfunktion
Will man aber die Vorbesitzer und ihre Geräte, dann kommt man mit SUB so nicht weiter.
(für die Profis: es sein denn, man biegt die Beziehung durch eine explizite Relation-Anweisung um)
Die einzige Änderung ist der Index beim Aufruf von Access und schon kann man die Geräte ermitteln, welche die aktuelle Adresse als Vorbesitzer hatte.
VAR ADRESSEN : Integer = Opendb("../database/adressen.dat")
VAR GERAET
: Integer = Opendb("../database/geraet.dat")
Var r,i : Integer
r := firstrec(A)
while readrec(ADRESSEN,r)
cgiwriteln("Besitzer : "+Getfield(ADRESSEN,"Name"))
r := NextRec(ADRESSEN)
access(GERAET,"$Vorbesitzer"); setfilter(GERAET,getfield(ADRESSEN,"AutoID") ,getfield(ADRESSEN,"AutoID"),1)
i := FirstRec(GERAET)
While ReadRec(GERAET,i)
CGIWriteLn(GetField(GERAET,"Inventurnummer"))
End
end
8.3
Über Array
Hier speichert man die Auto-Nummern der Einträge in ein Array und durchläuft dann das Array:
VAR ADRESSEN : Integer = Opendb("../database/adressen.dat")
VAR GERAET
: Integer = Opendb("../database/geraet.dat")
Var r,i : Integer
Var ai : Integer[]
FindAndMark(ADRESSEN,"$Name wie 'A*'")
InitArray(ai[NMarks(ADRESSEN)]);
GetMarksAuto(ADRESSEN,ai)
i :=-1
While ai[i++]
ReadRec(ADRESSEN,FindAuto(ADRESSEN,ai[i]))
cgiwriteln("Besitzer : "+Getfield(ADRESSEN,"Name"))
End
- 73 -
Betriebssystemfunktion
Der Zugriff auf die angekoppelten Geräte geht ebenso:
VAR ADRESSEN : Integer = Opendb("../database/adressen.dat")
VAR GERAET
: Integer = Opendb("../database/geraet.dat")
Var g,i : Integer
Var ai,gi : Integer[]
FindAndMark(ADRESSEN,"$Name wie 'A*'")
InitArray(ai[NMarks(ADRESSEN)]);
GetMarksAuto(ADRESSEN,ai)
i :=-1
While ai[i++]
ReadRec(ADRESSEN,FindAuto(ADRESSEN,ai[i]))
cgiwriteln("Besitzer : "+Getfield(ADRESSEN,"Name"))
access(GERAET,"$Besitzer"); setfilter(GERAET,getfield(ADRESSEN,"AutoID") ,getfield(ADRESSEN,"AutoID"),1)
InitArray(gi[NMarks(GERAET)]); GetMarksAuto(GERAET,gi)
g := -1
While gi[g++]
ReadRec(GERAET,FindAuto(GERAET,gi[g]))
CGIWriteLn(GetField(GERAET,"Inventurnummer"))
End
End
- 74 -
Betriebssystemfunktion
9.
Indexfunktionen
9.1
ALLGEMEINES ZU INDIZES
Ein Index ist eine Art Stichwortverzeichnis einer Tabelle, das in einer speziellen Ordnung angelegt wird und dadurch eine sehr schnelle Suche nach Datensätzen
erlaubt. Zur Bildung eines solchen Stichwortverzeichnisses können im Prinzip alle Feldtypen verwendet werden, ausgenommen sind hier nur Memos, Blobs und
Relationen.
Indizes werden benötigt
zur schnelle Suche nach bestimmten (indizierten Feldern)
zum (relationalen) Verknüpfen von Tabellen
zum geordneten (sortierten) Zugriff auf die Zeilen einer Tabelle
Wird beispielsweise in einer Adresstabelle ein Index über das Feld "Name" erzeugt, so wird eine neue Tabelle mit zwei Feldern angelegt: Das erste enthält den
Inhalt des jeweiligen Namenfeldes, das zweite die physikalische Satznummer des zugehörigen Datensatzes. Diese Tabelle wird in einem ganz speziellen Format
angelegt, es handelt sich um einen sog. B-Tree (eine hocheffiziente Ordnungs- und Suchstruktur). Und weil die Indizes in externen Dateien abgelegt werden,
spricht man bei den Indizes der tdbengine auch von externen Indizes.
Eine Reihe von Indizes wird automatisch angelegt:
Falls die Tabelle ein AUTO-INCREMENT-Feld enthält, wird über dieses Feld eine Index erzeugt (Extension .inr).
Falls die Tabelle ein AUTO-INCREMENT-Feld enthält, wird gemäß der Struktur ein ID-Index erzeugt (Extension .id)
Jedes Link-Feld, das ohne U-Spezifikation in einer Tabelle enthalten ist, führt zu einem Index über dieses Feld (Extension .in1 bis .in9).
Zusätzliche Indizes können ebenfalls in der Strukturdefinition angegeben werden:
ind-name = Name der Indexdatei mit Extension .ind
ind-def = Indexbeschreibung (siehe unten)
Wichtig: Die Namen der Indizes sollten derzeit (ohne Extension) nicht mehr als 8 Zeichen umfassen und nur Buchstaben (ohne Umlaute) enthalten.
9.2
DIE INDIZES DES TDBENGINE
Über jede Tabelle können beliebig viele (in der aktuellen Version 6.2.6 bis zu 15) Indizes erzeugt werden. Die tdbengine bietet hierarchische Indizes mit bis zu
10 Hierarchiestufen und berechnete Indizes.
- 75 -
Betriebssystemfunktion
Die Definition eines Index erfolgt über eine Indexbeschreibung. Diese hat folgendes Format:
Hierarchischer Index::=Feld-Def{","Feld-Def}.
Feld-Def::=["$"]Feld[":"Zahl]
Feld::=Feldbezeichner|Feldnummer
Berechneter Index::="("Ausdruck")"[":"Zahl]
Beispiele:
9.2.1
Hierarchischer Index
Hierarchischer Index: Name,Vorname:1,PLZ
Berechneter Index: (Name+'/'+Vorname):40
Die optionale Angabe :Zahl nach einem Feld (bzw. nach einem Ausdruck) wird nur bei String-Feldern (bzw. bei String-Ausdrücken) ausgewertet und gibt an,
wieviele Zeichen des Strings (maximal) in die Indexinformation übernommen werden.
Folgende Einschränkungen für Indexbeschreibungen gelten derzeit (Version 6.2.9):
9.2.2
Berechneter Index
Der Ausdruck eines berechneten Index darf derzeit 40 Zeichen nicht überschreiten.
Die folgende Anweisung hat 39 Zeichen und geht daher gerade noch:
UnixTime_To_Str($Bestelleingang)[1,10])
Die Indexinformation (Summe aller Hierarchiestufen) darf maximal 256 Bytes umfassen.
Der Ausdruck muss in Klammern stehen und man ein String sein.
Beispiel:
index_11=daten11.ind:(UnixTime_To_Str($Bestelleingang)[1,10])
9.3
Index erzeugen
GenIndex(db : REAL; IndDef : STRING; IndName : STRING) : REAL
db : Tabellenhandle von OpenDB()
IndDef : Indexbeschreibung als String
IndName : Dateiname des Index (ohne Pfad, mit .ind' als Extension)
- 76 -
Betriebssystemfunktion
Rückgabewert:
0 : Funktion konnte nicht ausgeführt werden (Fehlercode mit TDB_LastError)
1..15 : Nummer des neu erzeugten Index
Häufigster Fehler: "Index existiert bereits". Die tdbengine erzeugt keinen Index, wenn eine (Index-)Datei gleichen Namens im Verzeichnis der zu indizierenden
Tabelle ist.
Wichtig: Der Index wird immer im gleichen Verzeichnis wie die zugehörige Tabelle abgelegt. Der Name der Indexdatei sollte immer die Extension .ind haben.
Die Dateinamen sollten der 8.3-Konvention entsprechen, also xxxxxxxx.ind. Auch Verzeichnisangaben sind nicht zulässig!
Der Parameter Indexkann entweder durch den Indexnamen (mit Extension) oder durch die Indexnummer angegeben werden
Hinweis: Die Funktion wird nur dann ausgeführt, wenn die Tabelle mit dem Recht "Indizieren" (=8) geöffnet wurde.
9.3.1
genindex() Erzeugt einen neuen Index
Beschreibung
Häufigster Fehler: "Index existiert bereits". Die tdbengine erzeugt keinen Index, wenn eine (Index-)Datei gleichen Namens im Verzeichnis der zu indizierenden
Tabelle ist.
Wichtig: Der Index wird immer im gleichen Verzeichnis wie die zugehörige Tabelle abgelegt. Der Name der Indexdatei sollte immer die Extension .ind haben. Die
Dateinamen sollten der 8.3-Konvention entsprechen, also xxxxxxxx.ind. Auch Verzeichnisangaben sind nicht zulässig!
Syntax
GenIndex(db : INTEGER; IndDef : STRING; IndName : STRING) : INTEGER
Parameter
db : Tabellenhandle von OpenDB
IndDef : Indexbeschreibung als String
IndName : Dateiname des Index (ohne Pfad, mit .ind' als Extension)
Ergebnis
0 : Funktion konnte nicht ausgeführt werden (Fehlercode mit TDB_LastError)
1..15 : Nummer des neu erzeugten Index
- 77 -
Betriebssystemfunktion
9.4
Index löschen
DelIndex(db : REAL; Index) : REAL
db : Tabellenhandle von OpenDB()
Index: Indexname (ohne Pfad, aber mit Extension) oder Indexnummer
Rückgabewert: 0 = Funktion erfolgreich ausgeführt, sonst Fehlercode
Häufigster Fehler: "Index ist noch in Gebrauch". Solange der Index der aktive Zugriff einer geöffneten Tabelle ist, kann er nicht entfernt werden.
Achtung: Der betreffende Index wird definitiv gelöscht und aus der Tabelle entfernt. Die Funktion sollte mit größter Vorsicht eingesetzt werden: Da Tabellen zu
mehreren Projekten gehören können, weiss ein Projekt oft nichts von der Notwendigkeit eines Index in einem anderen Projekt. Nach DelIndex werden alle
höheren Indexnummer um 1 dekrementiert.
9.5
Index auffrischen
RegenInd(db : REAL; Index) : REAL
db : Tabellenhandle von OpenDB()
Index: Indexname (ohne Pfad, aber mit Extension) oder Indexnummer
Rückgabewert: 0 = Funktion erfolgreich ausgeführt, sonst Fehlercode
Alle Indizes werden automatisch mit der zugehörigen Tabelle aktualisiert, so dass sich im Normalfall keine Notwendigkeit zum Auffrischen ergibt. Allerdings wird
im Laufe der Zeit (nach vielen Änderungen an der Tabelle) die Auslastung des zugrunde liegenden B-Trees immer schlechter, so dass dann das Auffrischen
eines Index dazu führt, dass
die Suchzeit etwas verbessert wird (allerdings kaum messbar)
der Platzbedarf auf der Festplatte deutlich reduziert wird.
9.6
Alle Indizes auffrischen
RegenAll(db : REAL) : REAL
db : Tabellenhandle von OpenDB()
Rückgabewert: 0 = Funktion erfolgreich ausgeführt, sonst Fehlercode
- 78 -
Betriebssystemfunktion
Damit werden sämtliche Indizes einer Tabelle aufgefrischt.
Achtung: Die Funktionen zur Index-Aufrischung dürfen nicht auf Tabellen angewandt werden, die über ScanRecs() im Minimalmodus erzeugt wurden.
9.7
Indexbeschreibung ermitteln
IndDef(db : REAL; Index) : STRING
9.7.1
inddef() - Indexbeschreibung ermitteln
Syntax
INDDEF(db : INTEGER; index : INTEGER|STRING [; mode : INTEGER]) : STRING
Parameter
db : Tabellenhandle von OpenDB
index : Indexnummer oder Indexname (als STRING)
mode : 0|1
Ergebnis
Indexbeschreibung des angegebenen Index
mode 0 (Vorgabe) : Mit Längenangabe
mode 1 : Ohne Längenangabe
Beispiel:
i:=GenIndex(db,'Name,Vorname:20','adressen.ind')
IndDef(db,i)
-> "Name:40,Vorname:20"
IndDef(db,i,1)
-> "Name, Vorname
9.8
indname() - Liefert den Namen eines Index
Syntax
- 79 -
Betriebssystemfunktion
INDNAME(db : INTEGER; n : INTEGER) : STRING
Parameter
db : Tabellenhandle von OpenDB
n : Indexnummer
Ergebnis
Name des Index mit der Nummer n
9.9
regenall() - Regeneriert sämtliche Indizes einer Tabelle
Beschreibung
Die Indizes einer Tabelle werden bei der tdbengine immer dynamisch mitgeführt. Allerdings kann sehr häufiges Löschen von Datensätzen dazu führen, dass ein
Index mehr Platz belegt als eigentlich benötigt würde. In einem solchen Fall kann die Regeneration der Indizes sowohl die Speicherbelegung als auch das
Zugriffsverhalten günstig beeinflussen.
Syntax
REGENALL(db : INTEGER) : INTEGER
Parameter
db : Tabellenhandle von OPENDB
Ergebnis
Fehlercode,
0 = erfolgreiche Ausführung
9.10
regenind() - Regeneriert den Index einer Tabelle
Beschreibung
Die Indizes einer Tabelle werden bei der tdbengine immer dynamisch mitgeführt. Allerdings kann sehr häufiges Löschen von Datensätzen dazu führen, dass ein
Index mehr Platz belegt als eigentlich benötigt würde. In einem solchen Fall kann die Regeneration des Index sowohl die Speicherbelegung als auch das
Zugriffsverhalten günstig beeinflussen. Ein Fehler kann aufreten, wenn die Tabelle nicht mit dem Recht zum Indizieren geöffnet wurde.
Syntax
REGENIND(db : INTEGER; indexno : INTEGER) : INTEGER
Parameter
db : Tabellenhandle von OPENDB
- 80 -
Betriebssystemfunktion
indexno : Nummer der zu regenerierenden Index
Ergebnis
Fehlercode
0 = Index erfolgreich regeneriert
9.11
indno() - Nummer des aktiven Index ermitteln (veraltet)
Beschreibung:
Wird durch ACCESS ersetzt
Syntax
INDNO(db : INTEGER) : INTEGER
Parameter
db:Tabellenhandle von OpenDB
Ergebnis
Nummer des aktuellen Index
- 81 -
Betriebssystemfunktion
9.12
access() - Legt die Zugriffsreihenfolge für eine Tabelle fest
Beschreibung
Wird kein Modus angegeben, so liefert Access() den aktuellen Zugriff.
Die Zugriffssteuerung wird berücksichtigt von
SUB ... ENDSUB
firstrec()
lastrec()
nextrec()
prevrec()
Access() liefert keinen Fehler. Bei einem illegalen Argument wird einfach der Zugriff nicht umgesetzt.
Aus diesem Grund sollte man bei Index-Zugriffen immer das Funktionsergebnis beachten.
Syntax
ACCESS(db : INTEGER[; Mode : STRING|INTEGER]) : INTEGER
Parameter
db : Tabellen-Handle, wie er von OPENDB geliefert wird.
Mode:
"Nummer" oder -1 für physikalische Reihenfolge
"Markierung" oder -2 für Markierungsliste
Indexname oder Indexnummer für Zugriff über Index
Ergebnis
-2 : Zugriff steht auf "Markierung"
-1 : Zugriff steht auf "Nummer"
0..15 : Zugriff steht auf entsprechendem Index
- 82 -
Betriebssystemfunktion
9.13
delindex() - Löscht einen bestehenen Index und entfernt ihn aus der Tabelle
Beschreibung Häufigster Fehler: "Index ist noch in Gebrauch". Solange der Index der aktive Zugriff einer geöffneten Tabelle ist, kann er nicht entfernt werden.
Achtung: Der betreffende Index wird definitiv gelöscht und aus der Tabelle entfernt. Die Funktion sollte mit größter Vorsicht eingesetzt werden: Da Tabellen zu
mehreren Projekten gehören können, weiss ein Projekt oft nichts von der Notwendigkeit eines Index in einem anderen Projekt. Nach DelIndex werden alle
höheren Indexnummer um 1 dekrementiert.
Syntax
DELINDEX(db : INTEGER; Index : INTEGER|STRING) : INTEGER
Parameter
db : Tabellenhandle von OpenDB
Index: Indexname (ohne Pfad, aber mit Extension) oder Indexnummer
Ergebnis
0 : Funktion erfolgreich ausgeführt, sonst Fehlercode
- 83 -
Betriebssystemfunktion
9.14
treeinfo() - Index-Informationen
Beschreibung
LiefertdiewichtigstenInformationenüberden(einemIndexzugrundeliegenden)B-TreeinderForm:
page=xxxx,n=xxxx,h=xxxx,p=xxxx,nn=xxxx
page
Seitengröße in Bytes
n
Ordnung (in jeder Seite befinden sich mindestens n Elemente; Ausnahme Wurzelseite)
h
Höhe des Baumes (= Anzahl der Seiten, die maximal gelesen werden müssen, um einen Eintrag zu finden)
p
Anzahl der (belegten) Seiten im Baum
nn
Anzahl der Einträge im Baum
Syntax
TreeInfo(db : INTEGER; Mode : INTEGER) : STRING
Parameter
db : Tabellenhandle von OpenDB
Mode : Indexnummer oder Indexname (ohne Pfad)
Ergebnis
pagesize : index_pagesize
n : Ordnung; max. Speichereinträge 2 x n
h : Höhe des Baumes, max Anzahl Seiten, die beim Suchen gelesen werden müssen
p : Anzahl Seiten
nn : Anzahl Einträge im Index = FileSize
- 84 -
Betriebssystemfunktion
10.
ZUGRIFFSREIHENFOLGE ERMITTELN UND FESTLEGEN
Für jede geöffnete Tabelle kann eine Zugriffsreihenfolge eingestellt werden, die für folgende Funktionen relevant ist:
FirstRec() erster Datensatz
LastRec() letzter Datensatz
NextRec() nächster Datensatz
PrevRec() vorhergehender Datensatz
Die Zugriffsreihenfolge ist auch in einer SUB ... ENDSUB Anweisung relevant, wenn hier keine Sortierreihenfolge angegeben wird.
Access(db : REAL[; Index]) : REAL
Ohne Index-Paramter wird die aktuelle Zugriffsreihenfolge zurückgeliefert (erst ab Version 6.2.6):
-2 : Markierungsliste
-1 : Physikalische Ordnung
0 : AUTO-INCREMENT (falls vorhanden)
1: ID-Index (falls vorhanden)
1(2)..15 : entsprechend Indexnummer
Beim Index kann zusätzlich zu den Indexnamen (oder -nummern) noch folgendes angegeben werden:
-2 oder "Markierung"
-1 oder "Nummer"
IndName(db : REAL; Index) : STRING
Liefert den Namen des angegeben Index.
10.1
DATENSATZ ÜBER INDEX SUCHEN
FindRec(db : REAL; SearchStr : STRING [;Index[;Mode : REAL]]) : REAL
- 85 -
Betriebssystemfunktion
db : Tabellenhandle von OpenDB()
SearchStr : gesuchte Information
index : Indexname oder Indexnummer(>=0)
Mode : 0 (Vorgabe) -> Suche nach Eintrag >= SearchStr; 1 -> Suche nach Eintrag = SearchStr
Rückgabewert: 0 -> kein Datensatz gefunden, ansonsten RecNo(db)
Bemerkungen: Der SearchStr muss adäquat zur Index-Beschreibung aufgebaut sein. Enthält eine Komponente ein Komma, so muss
diese geklammert werden.
Beispiele:
Index-Beschreibung ="Name,Vorname,Ort"
SearchStr = "Schwalm,Till,München", "Lichtenberg,Franz,(München,Moosach)"
Werden die Informationen vom Anwender geholt, ist eine Klammerung für alle Komponenten sinnvoll:
SearchStr:='('+s_Name+'),('+s_Vorname+'),('+s_Ort+')'
Wird kein Index angegeben, so wird im aktuellen (mittels Access() eingestellten) Index gesucht. Steht der Zugriff nicht auf einem Index (sondern auf Nummer
oder Markierung), so erfolgt die Fehlermeldung "Illegaler Zugriff").
Mit dem Modus 0 wird der (bzgl. der Index-Ordnung) kleinste Eintrag gesucht, der gleich oder größer dem Gesuchten ist. Ist hier kein Eintrag vorhanden, so wird
der größte Eintrag gesucht, der gleich oder kleiner dem Gesuchten ist. In diesem Modus wird also nur dann 0 zurückgeliefert, wenn die Tabelle komplett leer ist.
Im Modus 1 wird hingegen nur dann ein Wert ungleich 0 geliefert, wenn ein Eintrag gefunden wird, der in allen Komponenten mit der Suche übereinstimmt.
RecNo(db)
Name
Vorname
Ort
1
Meier
Franz
München
2
Asbacher
Edeltraud
Frankfurt
3
Grünwalder
Antonia
Erfurt
4
Meier
Hans
Hamburg
5
Vogelsang
Ottfriede
Gelsenkirchen
- 86 -
Betriebssystemfunktion
Index 1 Beschreibung: "Name,Vorname,Ort"
Index 2 Beschreibung: "(Name+'/'+Vorname)"
Index 1 (intern)
Index Information
RecNo(db)
Ansbacher,Edeltraud,Frankfurt
2
Grünwalder,Antonia,Erfurt
3
Meier,Franz,München
1
Meier,Hans,Hamburf
4
Vogelsang,Ottfirede,Gelsenkirchen
5
FindRec(db,"Meier,Franz,München",1,0) -> 1
FindRec(db,"Meier,Franz,München",1,1) -> 1
FindRec(db,"Meier,Franz",1,0) -> 1
FindRec(db,"Meier,Franz",1,1) -> 0
Index 2 (intern)
Index Information
RecNo(db)
Ansbacher/Edeltraud
2
Grünwalder/Antonia
3
Meier/Franz
1
Meier/Hans
4
Vogelsang/Ottfirede
5
FindRec(db,"Meier/Hans",2,0) -> 4
FindRec(db,"Meier/Hans",2,1) -> 4
FindRec(db,"Meier",2,0) -> 1
FindRec(db,"Meier",2,1) -> 0
- 87 -
Betriebssystemfunktion
10.1.1
FILTER SETZEN
Durch einen Index wird eine Tabelle in eine Ordnung gebracht. Mit einem Filter wird bezüglich dieser Ordnung eine zusammenhängende Teilmenge (ein
Ausschnitt) aus der Tabelle definiert.
SetFilter(db : REAL; VON [, bis] : STRING) : REAL
db : Tabellenhandle von OpenDB()
von, bis : Indexinformation gemäß Indexbeschreibung
Rückgabewert: Immer 0
SetFilter() ist nur wirksam, wenn der Zugriff auf einen Index gesetzt wurde, also erst nach Access(db,...).
Ein Filter ist solange aktiv, bis er
mit SetFilter(db,'') aufgehoben wird, oder
die Primärtabelle gewechselt wird
Ist ein Filter aktiv, führt FirstRec ein FindRec(db,von) aus, liefert aber nur einen Wert, wenn der gefundene Datensatz gleich oder größer dem Inhalt von "von"
ist. Entsprechendes gilt für LastRec. NextRec bzw PrevRec liefern nur dann Ergebnisse, wenn die gefundenen Werte im aufgespannten Bereich liegen. Auch
SUB liefert nur Datensätze aus diesem Bereich.
Der Parameter bis kann nur weggelassen werden, wenn die zugehörige Indexbeschreibung ausschließlich String-Felder enthält:
SetFilter(db,von) steht dann als Abkürzung für SetFilter(db,von,von+chr(255)).
Bei einem hierarchischen Index werden nicht angegebene Komponenten bei von mit dem kleinsten, bei bis mit dem größten möglichen Wert aufgefüllt.
10.1.2
DIE FILTER-AUTOMATIK DER TDBENGINE
Bei der Bearbeitung von Selektionen versucht die tdbengine die zu lesende Datenmenge und damit den Selektionsaufwand zu minimieren, indem sie alle
vorhandenen Indizes daraufhin untersucht, ob ein passender Filter gesetzt werden kann. Diese Automatik arbeitet unabhängig von der Reihenfolge der logischen
Operationen und auch unabhängig von der verkürzten Logikauswertung.
Die Automatik wird abgeschaltet, wenn zum Zeitpunkt der Selektion
der Zugriff über die Markierungsliste erfolgt, oder
- 88 -
Betriebssystemfunktion
der Zugriff auf einen anderen Index als den .inr (über die Autonummern) gesetzt ist.
Beispiel für die Arbeitsweise der Filter-Automatik:
[STRUCTURE]
field_1=Name,STRING,40
field_2=Vorname,STRING,30
field_3=Strasse,STRING,40
field_4=PLZ,STRING,5
field_5=Ort,STRING,30
[INDEX]
index_1=adr_name.ind:Name,Vorname
index_2=adr_plz.ind:PLZ,Ort
Selektion
Index
Filter von…bis
$Name like "Meier"
adr_name.ind
meier
$Name like "Meier*" and $Vorname like "Hans"
adr_name.ind
meier,hans
$PLZ from "70000" to "79999"
adr_plz.ind
70000 ... 79999,chr(255)+chr(255)...
adr_plz.ind
8
( "*gruber" erlaubt keinen Filter)
adr_name.ind
hinter
(4 Zeichen hei "hinter" gegenüber 1 Zeichen bei "8")
$Name like "*gruber" and $PLZ like "8*"
$Name like "hinter*meier*" and $PLZ like "8*"
Die Filter-Automatik sorgt dafür, dass die Funktion SetFilter nur in wenigen Spezialfällen eingesetzt werden muss.
- 89 -
Betriebssystemfunktion
11.
Volltextindizierung
Anders als die normale Indizierung, bei der der Inhalt einzelner Felder als Ganzes aus der Datenbank extrahiert und in einer speziellen, geordneten Struktur
abgelegt wird, wird bei der Volltextindizierung der Feldinhalt weiter auf gespalten in einzelne Wörter. Diese Wörter werden in einer eigenen Tabelle abgelegt, und
eine Relation aus dieser Tabelle (der Stichwortliste) und der Ausgangstabelle bildet schließlich denVolltextindex.
Die Volltextsuche ist nicht nur unglaublich schnell, sie ermöglicht auch eine Suche nach jedem Wort, das indiziert wurde.
Die tdbengine bietet ferner eine feldbezogene Volltextsuche. Damit lässt sich das Suchergebnis auf ein Feld begrenzen.
Die Möglichkeiten der Volltextsuche sind schier unerschöpflich.
Selbst wenn man die Suche so kompliziert sein sollte, dass diese nicht in einem Durchlauf möglich ist, kann man immer noch das Suchergebnis zweier oder
mehrerer Suchläufe plitzschnell binäre schneiden und damit die gewünschte Treffermenge erhalten.
Dieses Möglichkeit ist eine der tdbengine spezifischen Fähigkeiten, die man in der Form in keiner anderen Datenbank findet.
Hier lohnt es sich, sich in die Materie einzuarbeiten.
SQL Datenbank stoßen hier trotz Ihrer Mächtigkeit an physikalische Grenzen.
Hier kann man mit der tdbengine Anwendungen schaffen, die ein echtes Alleinstellungsmerkmal besitzen.
Aber zuerst einmal möchte ich die verschiedenen Funktionen für die Volltextindizierung und für dir Volltextabfrage vorstellen.
1
Die Erstellung eines Volltextes sollte man den baseman überlassen.
Sowohl für die Indizierung einzelner Datensätze als auch für die Indizierung einer ganzen Tabelle stehen zwei in EASY geschriebene Funktionen bereit.
FulltextScanRec und FulltextScanRecs.
Für die Löschung von Indexeinträgen gibt es die Funktion FulltextUnScanRec
Für alle, die mehr wissen wollen sind die internen Funktionen im folgenden näher beschrieben.
1
Baseman.prg – ein Tool für die Erstellung und Administration der TDB-Tabellen
- 90 -
Betriebssystemfunktion
11.1
Volltextdatein anlegen (mit Hilfe des baseman.prg)
Der baseman.prg ist ein Tool, welches alle Funktionen zur Erstellung und Pflege von Tabellen beinhaltet.
Der Volltext wird innerhalb der Def-Datei als eigene Section definiert.
Erstellen Sie eine Textdatei mit folgendem Inhalt und speichern Sie die textdatei im Verzeichnis /def.
Im Verzeichnis /system muss sich das Programm baseman.prg befinden.
[STRUCTURE]
Name,STRING,50
Street,STRING,50
Postcode,STRING,12
City,STRING,50
Countrycode,STRING,3
Phone,STRING,40
Fax,string,40
Notes,MEMO
AutoID,AUTO
[INDEX]
inr=$AutoID
id=$Name
[FULLTEXT]
fulltext=JA
Fields=complete
ExtABC=0123456789
Starten Sie den anschließend den baseman.prg im Browser
Hier wird sofort erkannt, dass die Tabellendefinition geändert wurde. Daher wird als Aktion Restrukturieren angeboten.
Klicken Sie nun auf den grünen Haken, um die Restrukturieren durchzuführen.
- 91 -
Betriebssystemfunktion
Alle benötigten Dateien werden automatisch angelegt:
Und man hat einen aktuellen Volltextindex.
11.1.1
Interne Informationen zum manuellen Aufbau eines Volltextes
Die Voraussetzung für die Volltextindizierung einer Tabelle besteht im Vorliegen zweier Tabellen:
Stichworttabelle, die im ersten Feld eine Zeichenkette enthält und automatisch nummeriert wird
Relationstabelle zwischen der Ausgangstabelle und der Stichworttabelle.
Weil diese Tabellen recht häufig benötigt werden, bietet die tdbengine zu deren Erzeugung zwei Sonderfunktionen der allgemeinen Funktion MakeDB :
GenList erzeugt eine neue Stichworttabelle
GenRel erzeugt eine neue Relationtabelle
GenList(Dateinname, Zeichenzahl, Modus)
Damit wird eine neue, leere Tabelle unter dem Dateinamen angelegt. Die Struktur dieser Tabelle ist, wenn kein Modus oder Modus =0 angegeben wurde:
[STRUCTURE]
field_1=Wort,STRING,Zeichenzahl
field_2=Laufende_Nummer,AUTO,1
[INDEX]
id=Wort,Laufende_Nummer
Für den Modus =1 ergibt sich ein kleiner Unterschied:
- 92 -
Betriebssystemfunktion
[STRUCTURE]
field_1=Wort,STRING.Zeichenzahl
field_2=WieOft,NUMBER,4
field_3=Laufende_Nummer,AUTO,4
[INDEX]
id=Wort, Laufende_Nummer,WieOft
GenRel(Tabelle1,Tabelle2,Dateiname)
Legt eine leere Relationstabelle mit folgender Struktur an
[STRUCTURE]
field_1=R-Tabelle1,LINK,Tabelle1
field_2=R-Tabelle2,LINK,Tabelle2
Hinweis für Experten: Die Ausgangstabelle muß weder automatisch nummeriert werden, noch ein Relationsfeld zur Stichworttabelle enthalten!
Beide Tabellen werden normalerweise über die üblichen Tabellenfunktionen weder gelesen noch geschrieben. Denn dafür sind die speziellen Volltextfunktionen
der tdbengine zuständig:
11.2
Volltext komplett auffrischen - FulltextScanRecs
Zur Auffrischung des Volltextes können Sie die folgende Funktion verwenden.
Die Funktion setzt voraus, dass die Volltextinformationen in der def-Datei als Section angelegt sind.
Des weiteren wird davon ausgegangen, dass die beiden benötigten Tabellen d-ind.dat und d-rel.rel heißen und sich in einem eigenen Verzeichnis befinden.
Findet er in der Def-Datei bestimmte Informationen nicht, so nimmt er automatisch bewährte Standardwerte.
Procedure FulltextScanRecs(db:Integer;sVT,cDef:String)
Var D : Integer = system.OpenDB(sVT+"/d-ind.dat","",0,15)
Var R : Integer = system.OpenDB(sVT+"/d-rel.rel","",0,15)
VAR cFields : String = GetIdent(cDef,'FULLTEXT.Fields')
VAR cExtABC : String = GetIdent(cDef,'FULLTEXT.ExtABC')
Var nCut : Integer = VAL(GetIdent(cDef,'FULLTEXT.Cut'))
VAR nModus : Integer = VAL(GetIdent(cDef,'FULLTEXT.Modus'))
VAR nStep : Integer = VAL(GetIdent(cDef,'FULLTEXT.Step'))
cFields := Choice(Sel(cFields=""),'complete',cFields)
cExtABC := Choice(Sel(cExtABC=""),'0123456789.',cExtABC)
- 93 -
Betriebssystemfunktion
nCut := Choice(nCut+1,1000000,nCut)
nModus := Choice(nModus+1,4,nModus)
nStep
:= Choice(nStep+1,1000000,nStep)
SetPara("em 1")
ScanRecs(db,D,R,Fields(cFields),cExtABC,nCut,GetIdent(cDef,'FULLTEXT.Kontraindex'),nModus,nStep,VAL(GetIdent(cDef,'FULLTEXT.Mas
kenFeld')),GetIdent(cDef,'FULLTEXT.DynKontraIndex'))
SetPara("em 0")
system.CloseDB(R);system.CloseDB(D);
EndProc
Vor dem Aufruf der Funktion müssen Sie die Haupttabelle geöffnet haben. Die Tabellen muss nicht zum Schreiben geöffnet sein.
Hinweis für den Experten:
Allerdings macht es Sinn, den schreibenden Zugriff auf die Volltextdateien über eine Semaphore abzusichern.
Da in der Regel für die Haupttabelle bei einem Schreibvorgang eine Semaphore eingerichtet wird, empfiehlt es sich, die Haupttabelle schreibend zu öffnen und
dadurch auch den Schreibvorgang auf die Volltextdateien abzusichern.
Wenn Sie obige Procedure verwenden müssen Sie sich keine Gedanken über die Internas der Funktion ScanRecs machen.
11.2.1
ScanRecs(D,I,R,Fields(Felder),ExtABC,Cut,Kontraindex,Modus,Step,MaskenFeld,DynKontraIndex) : Integer
Nicht erschrecken über dieses Funktionsmonster: Alle Parameter mit Ausnahme der ersten vier sind optional!
D Tabellennummer der Ausgangstabelle
I Tabellennummer der Stichworttabelle
R Tabellennummer der Relationstabelle
Fields() ist eine Spezialfunktion, über die die Information, welche Felder in den Volltextindex aufgenommen werden, an die Funktion übergeben werden. Die
Feldkombination kann sowohl statisch als auch dynamisch erfolgen. Im Falle der CGI-Programmierung ist nur die dynamische Version möglich, weil hier ja zur
Übersetzzeit keine Tabellen geöffnet sind.
Statische Version: Fields(Feld1,Feld2,Feld3...)
Dynamische Version: Fields("Feld1,Feld2,Feld3...")
- 94 -
Betriebssystemfunktion
Es sind alle Felder der Tabelle (mit Ausnahme von Blobs) zugelassen. Datums-, Zeit- und Zahlenfelder werden in Zeichenketten umgewandelt. Bei
Auswahlfeldern wird die entsprechende Textkonstante in den Volltext übernommen.
Bei String-Feldern (Zeichenketten) gibt es eine wichtige Ausnahme: Beginnt ein String-Feld mit dem "#"-Zeichen, so wird der Rest der Zeichenkette als Pfad zu
einem externen Textdokument interpretiert und dieses in den Volltextindex aufgenommen. Die aktuelle Version der tdbengine unterstützt externe ASCII, ANSI
und HTML-Dokumente.
Zusätzlich können die Inhalte ADL-verknüpfter Datensätze über die L-Feld-Notation und über die R-Feld-Notation in den Volltextindex aufgenommen werden.
Darauf sollten Sie achten:
Reihenfolge beim Öffnen der Tabellen, die Relationstabelle immer als Letzte.
Vor ScanRecs() Primärtabellewechseln, damit die Feldbezeichner der Ausgangstabelle erkannt werden.
Und das passiert bei der Volltextindierung:
Zunächst werden sowohl Stichwort- als auch Relationstabelle geleert. Dann werden sämtliche Datensätze der Ausgangstabelle gelesen. Bei jedem Datensatz
werden die unter Fields() angegebenen Felder gescannt, d.h. in einzelne Zeichenketten zerlegt, die nur aus Buchstaben bestehen. Jeder dieser Wörter wird
nun in die Stichworttabelle eingetragen, wenn es dort nicht bereits vorhanden ist. Schließlich erfolgt ein Eintrag in die Relationstabelle mit je einem Verweis auf
den Datensatz der Ausgangstabelle und dem zugehörigen Satz in der Stichworttabelle.
11.2.2
11.2.2.1
DIE OPTIONALEN PARAMETER
ExtABC
ist eine Zeichenkette, die zusätzlich zu den Buchstaben als Wortbestandteile gelten sollen. Wenn beispielsweise auch die Postleitzahl in den Volltextindex
aufgenommen werden sollen, so muß hier "0123456789" stehen.
Tipp: Der Bindestrich sollte nicht als Bestandteil eines Wortes aufgenommen werden (außer in Spezialfällen), denn damit werden die einzelnen Bestandteile
eines (mit Bindestrichen) zusammengesetzten Wortes nicht mehr (so einfach und schnell) über die Volltextsuche auffindbar.
Bei den nächsten beiden Parametern geht es um die Einschränkung der Volltextindex. In vielen Fällen will man nämlich informationsarme Wörter wie "und" "der"
u.a. nicht in den Volltextindex aufnehmen.
11.2.2.2
Cut
Wird hier einWert größer 0 angegeben, so werden Wörter, die öfter als Cut vorkommen, nicht in den Volltextindex aufgenommen.
- 95 -
Betriebssystemfunktion
11.2.2.3
Kontraindex
Ist der Name einer externen Textdatei, in der jene Wörter enthalten sind, die nicht in den Volltextindex aufgenommen werden. Dabei steht jedes Wort in einer
eigenen Zeile, eine Sortierung ist nicht notwendig.
Der nächste Parameter definiert das grundsätzliche Verhalten der tdbengine bei Aufbau des Volltextindex. Die einzelnen Modi werden einfach addiert:
11.2.2.4
Modus
Die Stichworttabelle wird neuer stellt
Modus
Bedeutung
0
Die Stichworttabelle wird neu erstellt; wird bei "complete" zu 4
1
Eine bestehende Stichworttabelle wird verwendet und nur neue Wörter eingetragen
2
Eine bestehende Stichworttabelle wird verwendet, aber keine neunen Wörter aufgenommen
4 *)
Es werden nur die unbedingt benötigten Dateien angelegt
8
HTML-Tags werden überlesen und HTML-Sonderzeichen nach ASCII übersetzt
16
reserviert
32
externe Texte liegen im ASCII-Format vor (andernfalls ANSI)
64
String-Felder, die mit "#" beginnen, werden als Pfade zu externen Dateien interpretiert, die dann in den Volltextindex aufgenommen werden.
#/home/html/archiv/12/index.html
128
Es werden nur String- und Memo-Felder indiziert, also keine Zahlen-, Zeit-, Datumsfelder etc. (meist sehr sinnvoll bei "complete"!)
256
Es werden nur die markierten Datensätze in den Volltextindex aufgenommen
512
Die Stichwörter werden in der Stichworttabelle als Datensatz angelegt. Damit ist es möglich, direkt in der Stichworttabelle zu lesen.
640
Modus wie 512, jedoch ohne Zahlfelder.
*) Dieser Modus ist nur bedingt kompatibel zum relationalen System der tdbengine. Er ist aber immer dann mit großem Vorteil einzusetzen, wenn der Zugriff auf
die Stichworttabelle und die Relationstabelle ausschließlich über die Volltextfunktionen erfolgt. Zudem wird der benötigte Speicherplatz auf weit weniger als 50%
reduziert und die Suchgeschwindigkeit signifikant erhöht.
11.2.2.5
Step
Dieser Parameter ist nur dann auf einen Wert ungleich 0 zusetzen, wenn wirklich sehr große Datenmengen bearbeitet werden. Es handelt sich dabei um den
Speicherplatz, den die tdbengine während es Indizierens vom Betriebssystem anfordert. Viel Speicherplatz bringt in diesem Fall eine erhöhte
Bearbeitungsgeschwindigkeit. Allerdings kann eine zu hohe Speicheranforderung dazuführen, dass ein Teil davon vom Betriebssystem ausgelagert wird, was
- 96 -
Betriebssystemfunktion
dann zu einem massiven Performance-Einbruch führt! Aber angenommen, sie haben ein 256 Mbyte-System, auf dem nur wenige speicherintensive Prozesse
laufen so können Sie beispielsweise hier den Wert 100000000 (=100Millionen) eingeben.
Tipp: Verändern Sie diesen Wert erst dann, wenn die Indizierungszeiten wirklich aus dem Rahmen fallen.
11.2.2.6
Maskenfeld
Hierbei handelt es sich um die Nummer eines Feldes der Ausgangsdatei, das eine 16-Bit-Integerzahl speichert (NUMBER, 2). Der Inhalt dieses Feldes wird in
den Volltextindex, genauer in die Relationstabelle (noch genauer: in den IN2 der Relationstabelle) aufgenommen und kann bei der Volltextsuche berücksichtigt
werden. Ein Sonderfall besteht, wenn hier-1 angegeben wird, denn dann können die Maskenwerte direkt (durch Doppelpunkt abgetrennt) in der Feldkombination
bei Fields angegeben werden. Die Auswertung bei der Volltextsuche geschieht dann dermaßen, dass hier eine Zahl angegeben wird und diese mit einem binären
AND mit der im Volltextindex gespeicherten Maske verknüpft wird. Es werden dann nur solche Verknüpfungen gefunden, bei denen das binäre AND einen Wert
ungleich 0 ergibt.
Das klingt kompliziert und ist auch kompliziert, eröffnet aber sehr schöne Möglichkeiten.
Dazu ein Angenommen Sie haben eine Adreßdatenbank, in der unterschiedliche Arten von Adressen gespeichert sind: Privatadressen, Firmenadressen,
Behördern etc.
Die Art der Adressen speichern Sie als Kennung in einem Integer-Feld ab:
1
2
4
8
...
Privat
Firma
Behörde
wissenschaftliche Institution
Bei Adressen, die in mehrere Kategorien fallen, addieren Sie einfach diese Werte. Wenn Sie nun die Feldnummer dieses Feldes bei der Volltextindizierung mit
angeben, so können Sie bereits bei der Suche jede beliebige Einschränkung bezüglich der Kategorien machen, ohne dass hier zu ein einziger Datensatz
gelesen werden muss!
Alternativ zur Auswertung eines Maskenfeldes können Sie auch direkt bei Fields Maskenzahlen vergeben. In diesem Fall hat die Maskenfeldnummer den Wert1, und die Maskenzahlen werden direkt nach den Feldnamen angegeben. Dazu wieder ein
ScanRecs(D,I,R,Fields("Vorname:1, Name:2, Strasse:4, Land:8, PLZ: 8, Ort:8, Bemerkung:16"),"",0,"",0,0,-1)
Auch hier wird ein Volltextindex aufgebaut. In die Relationstabelle wird jedoch, wenn es sich bei dem gerade untersuchten Feld um Vorname handelt, zu jedem
Eintrag die Maskenzahl 1 aufgenommen, für das Feld Name die Zahl 2 usw. Bei der Volltextsuche können Sie dann gezielt in einzelnen Feldern oder
Feldkombinationen suchen (oder natürlich auch in allen Feldern gleichzeitig)!
- 97 -
Betriebssystemfunktion
11.2.2.7
DynKontraIndex
Hierbei handelt es sich um einen dynamischen, feldbezogenen Kontraindex, der wieder um über die Funktion Fields tranportiert wird. Das bedeutet, dass bei
jedem Datensatz zunächst die hierstehenen Felder gelesen werden und daraus ein Kontraindex gebildet wird. Dieser Kontraindex gilt allerdings nur für diesen
Datensatz, beim nächsten beginnt das Spiel von vorne.
11.2.3
Fehlerbehandlung
Die Parameter erlauben ein sehr flexibles Volltextindizieren beliebiger Datenbestände. Allerdings handelt es sich bei ScanRecs() um eine Tabellenfunktion, mit
der gleich zwei Tabellen gleichzeitig verändert werden, und entsprechend pingelig ist das System bei der Einhaltung der richtigen Syntax. Die Funktion liefert,
falls kein Fehler auftritt, die Anzahl der Einträge in die Relationstabelle. Andernfalls wird ein Laufzeitfehler ausgelöst, den Sie mit .EC1 abfangen können.
Die häufigsten Fehler:
50: (Unbekannter Bezeichner) In Fields() steht mindestens ein falsches Datenfeld
58: (Keine Tabelle) Mindestens eine der drei benötigten Tabellen ist nicht geöffnet
64: (Keine ADL-Tabelle) Die Stichworttabelle wird nicht automatisch nummeriert
77: (Keine Relationstabelle) Bei dem dritten Parameter handelt es sich nicht um eine gültige Relationstabelle
Ist das Funktionsergebnis negativ, wird zwar kein Laufzeitfehler ausgelöst, aber die Funktion dennoch abgebrochen
-115 Zu wenig Rechte für die Funktion (Die Relationsdatei konnte nicht geleert werden)
-56 (, erwartet) Die Felder in Fields müssen durch Komma getrennt werden
11.3
Einzelnen Datensatz indizieren mit FulltextScanRec
Procedure FulltextScanRec(db,id:Integer;sVT,cDef:String)
// Löscht den Volltexteintrag in der Tabelle d-rel.rel
Var i : Integer
Var D : Integer = system.OpenDB(sVT+"/d-ind.dat","",0,15)
Var R : Integer = system.OpenDB(sVT+"/d-rel.rel","",0,15)
VAR cFields
: String
VAR cExtABC
: String
Var nCut
: Integer
VAR nModus
: Integer
VAR nStep
: Integer
If IsFile(cDef)
- 98 -
Betriebssystemfunktion
cFields := GetIdent(cDef,'FULLTEXT.Fields')
cExtABC := GetIdent(cDef,'FULLTEXT.ExtABC')
nCut
:= VAL(GetIdent(cDef,'FULLTEXT.Cut'))
nModus := VAL(GetIdent(cDef,'FULLTEXT.Modus'))
nStep
:= VAL(GetIdent(cDef,'FULLTEXT.Step'))
End
cFields := Choice(Sel(cFields=""),'complete',cFields)
cExtABC := Choice(Sel(cExtABC=""),'0123456789.',cExtABC)
nCut
:= Choice(nCut+1,1000000,nCut)
nModus := Choice(nModus+1,4,nModus)
nStep := Choice(nStep+1,1000000,nStep)
readAuto(db,id)
SetPara("em 1")
i :=
ScanRec(db,D,R,Fields(cFields),cExtABC,nCut,GetIdent(cDef,'FULLTEXT.Kontraindex'),nModus,nStep,VAL(GetIdent(cDef,'FULLTEXT.MaskenF
eld')),GetIdent(cDef,'FULLTEXT.DynKontraIndex'))
SetPara("em 0")
system.CloseDB(R);system.CloseDB(D);
EndProc
Die Funktions ScanRecs() indiziert eine kompette Tabelle. Es ist aber auch möglich einen einzelnen Datensatz in einen bestehenden Volltextindex
aufzunehmen. ScanRec() indiziert den aktuellen Datensatz einer Tabelle.
ScanRec(D,I,R,Fields(Felder),ExtABC,Cut,Kontraindex,Modus,Step,MaskenFeld,DynKontraIndex) : REAL
Die Parameter sind genau dieselben wie bei ScanRecs(). Allerdings werden nur folgende Modi unterstützt:
2 Es werden keine neuen Wörter in die Stichworttabelle aufgenommen
4 Es werden nur die unbedingt benötigten Informationen gespeichert
Wichtig: Der Modus 4 kann nur verwendet werden, wenn der Volltextindex mit ScanRecs() in diesem Modus erzeugt wurde.
Das Funktionsergebnis liefert die Anzahl der neu hinzugekommenen Verknüpfungen bzw. einen der folgenden Fehlercodes:
-56 In Fields() fehlt (mindestens) ein Komma
-6 der ID-Index der Stichworttabelle konnte nicht geöffnet werden
11.4
Den Volltext für einen einzelnen Datensatz löschen - FulltextUnScanRec
Procedure FulltextUnScanRec(db,id:Integer;sVT,cDef:String)
- 99 -
Betriebssystemfunktion
..Löscht den Volltexteintrag in der Tabelle d-rel.rel
Var i : Integer
Var D : Integer = system.OpenDB(sVT+"/d-ind.dat","",0,15)
Var R : Integer = system.OpenDB(sVT+"/d-rel.rel","",0,15)
VAR cFields
: String
VAR cExtABC
: String
Var nCut
: Integer
VAR nModus
: Integer
VAR nStep
: Integer
If IsFile(cDef)
cFields := GetIdent(cDef,'FULLTEXT.Fields')
cExtABC := GetIdent(cDef,'FULLTEXT.ExtABC')
nCut
:= VAL(GetIdent(cDef,'FULLTEXT.Cut'))
nModus := VAL(GetIdent(cDef,'FULLTEXT.Modus'))
nStep
:= VAL(GetIdent(cDef,'FULLTEXT.Step'))
End
cFields := Choice(Sel(cFields=""),'complete',cFields)
cExtABC := Choice(Sel(cExtABC=""),'0123456789.',cExtABC)
nCut
:= Choice(nCut+1,1000000,nCut)
nModus := Choice(nModus+1,4,nModus)
nStep := Choice(nStep+1,1000000,nStep)
readAuto(db,id)
SetPara("em 1")
i :=
UnScanRec(db,D,R,Fields(cFields),cExtABC,nCut,GetIdent(cDef,'FULLTEXT.Kontraindex'),nModus,nStep,VAL(GetIdent(cDef,'FULLTEXT.Mask
enFeld')),GetIdent(cDef,'FULLTEXT.DynKontraIndex'))
SetPara("em 0")
system.CloseDB(R);system.CloseDB(D);
EndProc
Es ist auch möglich, einzelne Datensätze aus einem bestehenden Volltextindex herauszunehmen. Damit ist eine vollständig dynamische Verwaltung des
Volltextindex möglich.
UnScanRec(D,I,R,Fields(Felder),ExtABC,Cut,Kontraindex,Modus,Step,MaskenFeld,DynKontraIndex) : REAL
Die Parameter sind exakt dieselben wie bei ScanRec(). Das Funktionsergebnis liefert die Anzahl der gelöschten Verknüpfungen (falls positiv) bzw. den
Fehlercode.
- 100 -
Betriebssystemfunktion
11.5
Die Suche im Volltextindex
Zur Auswertung eines Volltextindex gibt es zwei Funktionen: MarkTable() und MarkBits(). Sie haben nahe zu die gleichen Parameter
MarkTable (D,I,Suchstring,ExtABC,Ops,MaskenZahl,R1,R2,...) : REAL
MarkBits(D,I,Suchstring,ExtABC,Ops,MaskenZahl,BitFeld,R1,R2,...) : REAL
D
I
Ausduck
ExtABC
Ops
MaskenZahl
BitFeld
R1, R2...
Nummer der Ausgangstabelle
Nummer der Stichworttabelle
Suchstring
wie bei ScanRecs()
logische Operatoren
Integerzahl
Variable vom Typ TBits[xxx]
Nummer der Relationstabelle
Das Funktionergebnis ist die Anzahl der gefundenen Datensätze (falls positiv) bzw. einer der Fehlercodes:
-64
-77
-56
-45
ungültige Stichworttabelle
ungültige Relationstabelle
illegale Klammerung im Suchstring
ungültiges Zeichen im Suchstring
Die letzten bei den Fehler können mit TDB_ErrorMsg und TDB_ErrorOfs näher bestimmt werden.
Beim Suchstring handelt es sich um eine Zeichenkette mit folgendem Aufbau:
Suchstring
Ausdruck
Term
Faktor
::=
::=
::=
::=
Ausdruck
Term{Oder-OperatorTerm}
Faktor{Und-OperatorFaktor}
Suchwort|"("Ausdruck")"
Ein Suchwort ist eine Zeichenkette aus Buchstaben, den Zeichen aus ExtABC sowie den Sonderzeichen "?" und "*". Dabei steht "?" als Platzhalter für genau ein
beliebiges Zeichen, "*" für beliebig viele Zeichen.
- 101 -
Betriebssystemfunktion
Folgende Operatoren sind vordefiniert (wenn in Ops ein Leerstring übergeben wird):
Oder-Operator
Und-Operatoren
11.5.1
+
,
-
(Vereinigungsmenge, logisches ODER)
(Schnittmenge, logisches UND)
(Schnitt mit Komplementärmenge, logisches UND NICHT)
Beispiele:
Meier
findet alle Datensätze die das Wort Meier in beliebiger Groß- oder
Kleinschreibung enthalten.
Mei*er
findet die Datensätze, die Wörter enthalten, die mit Mei beginnen und mit er
enden.
Hans, Meier
im Datensatz muß sowohl das Wort Hans als auch Meier enthalten sein.
Hans+Meier
findet Datensätze in denen Hans oder Meier oder beides vorkommt.
Hans-Meier
findet Datensätze, in denen zwar Hans vorkommt, Meier aber nicht.
11.5.2
OPS
Im Parameter Ops können die Operatoren umdefiniert werden. Dazu muß hier eine Zeichenkette mit genau drei Zeichen übergeben werden. Das erste Zeichen
ist dann der Operator für logisches UND, das zweite entspricht dem logischen ODER, das dritte schließlich dem logischen UND NICHT
11.5.2.1
Maskenzahl
Die Angabe einer Maskenzahl ist nur dann sinnvoll, wenn bereits bei der Volltextindizierung mit einem Maskenfeld oder mit Maskenkonstanten gearbeitet wurde.
Die hier angegebene Zahl und die in der Verknüpfung gespeicherten Zahl werden mit einem binären AND verknüpft. Es werden nur solche Datensätze gefunden,
bei denen das binäre AND einen Wert ungeich 0 ergibt.
Sonderfall: Die Maskenzahl 0 liefert alle Verknüpfungen.
Die Volltextindizierung erfolgte mit folgender Indexbeschreibung: Fields(Name:1,Vorname:2,Straße:4)
- 102 -
Betriebssystemfunktion
Die Suche nach Hans liefert dann je nach Maskenzahl
0 In (wenigstens) einem der Felder ist das Wort enthalten
1 Das Wort ist im Feld Name enthalten
2 Das Wort ist im Feld Vorname enthalten
3 Das Wort ist im Feld Name oder im Feld Vorname (oder in beiden) enthalten
4 Das Wort ist im Feld Straße enthalten
5 Das Wort ist im Feld Name oder im Feld Straße (oder in beiden) enthalten
...
7 Das Wort ist (wenigstens) in einem der Felder Name, Vorname oder Straße enthalten.
11.5.2.2
Bitfeld
In diese Parameter unterscheiden sich die beiden Suchfunktionen. Bei MarkTable() werden die Treffer direkt in die Markierungsliste der Tabelle eingetragen.
Bei MarkBits() hingegen werden die Treffer in ein Bitfeld übertragen. Das ist dann von Vorteil, wenn mehrfach gesucht wird und die einzelnen Suchergebnisse
zu einem Gesamtergebnis zusammengestzt werden.
VAR Treffer, Temp : TBITS[1000000]
MarkBits (...,Treffer,...)
MarkBits(...,Temp,...)
BitAnd (Treffer,MARKS )(ergibt die Schnittmenge aus beiden Suchen)
BitOr (Treffer,MARKS) (ergibt die Vereinigungsmenge aus beiden Suchen)
Expertenhinweis: Ein Bitfeld, wie wir es hier einsetzen, kann auch als charakteristische Mengenfunktion aufgefasst werden, also eine Abbildung der Menge aller
Datensätze einer Tabelle in die Menge {0,1}. Es handelt sich dabei um eine sehr effiziente Implementierung von (Teil-) Mengen, da die Mengenfunktionen dann
direkt (als fundamentale Maschinenoperationen) ausgeführt werden. Auch MarkTable() arbeitet intern mit einem Bitfeld und bildet erst am Ende der Funktion
die gefundene Menge auf die Markierungsliste ab.
Um auf die Treffer einer Suche zuzugreifen, gibt es mehrere Möglichkeiten:
11.5.2.3
Abbildung auf die Markierungsliste
Nach PutMarks(db,Treffer) stehen die Treffer in der Markierungsliste und können wie üblich weiterverarbeitet werden.
- 103 -
Betriebssystemfunktion
11.5.2.4
Direkter Zugriff auf das Bitfeld
i:=0
WHILE i<FileSize(D) DO
IF Treffer[i] THEN
..Bearbeitung des Datensatzes mit der Satznummer i
END
i:=i+1
END
11.5.3
REALTIONSTABELLEN
Beide Suchfunktionen können (gleichzeitig) in mehreren Relationen (zwischen der Ausgangstabelle und der Stichworttabelle) suchen. Im Normalfall wird jedoch
nur eine Relationstabelle angegeben.
Ein Szenario für zwei Relationstabellen könnte etwa folgendermaßen aussehen: Eine Tabelle für ein Textarchiv enthält eine Reihe von Informationen in
gewöhnlichen Datenfeldern und einen Verweis auf ein Textdokument, das im HTML-Format vorliegt. In diesem Fall kann es sinnvoll sein, für den Volltextindex
über die externen Textreferenzen eine eigene Relationstabelle zu erstellen.
11.5.4
DOKUMENTENARCHIV
Wir gehen von folgender (vereinfachter) Struktur einer Tabelle aus
[STRUCTURE]
field_1=Autor,STRING,60
field_2=Titel,STRING,120
field_3=Kurzinhalt,MEMO
field_4=Dokument,STRING,60
Im Feld Dokument sind Referenzen wie #/home/kern/doc/beispiel_001.html abgelegt.
Über diese Tabelle wurde ein Volltextindex erzeugt mit
ScanRecs(...,Fields(Autor:1,Titel:2,Kurzinhalt:4,Dokument:8),"",0,"",4+8)
Modus4 (nur benötigte Dateien) +8 (Unterstützung von externen HTML-Dateien)
Unser Suchformular hat folgendenAufbau:
- 104 -
Betriebssystemfunktion
<formaction=... method="post">
Suchbegriff: <input type="text" name="suchbegriff" size="40"><br>
Suchein: <input type="checkbox" name="cb_autor" value="1"> Autor
<input type="checkbox" name="cb_titel" value="2"> Titel
<input type="checkbox" name="cb_kurzinhalt" value="4"> Kurzinhalt
<input type="checkbox" name="cb_dokument" value="8"> Dokument
</form>
Damit erlauben wir dem Anwender die Eingabe eines Suchbegriffs und geben ihm die Auswahl, in welchen Bereichen der Begriff gesucht werden soll.
PROCEDURE Dokument Suche
VAR D,I,R,Modus : INTEGER
...
..hier sind alle drei Tabellen geöffnet
..
Modus:=Val(CGIGetParam(cb_autor))+VAL(CGIGetParam(cb_titel))
Modus:=Modus+VAL(CGIGetParam(cb_kurzinhalt))+VAL(CGIGetParam(cb_dokument))
MarkTable(D,I,CGIGetParam("suchbegriff"),"","",Modus,R)
...
..Treffer ausgeben
..
ENDPROC
Es kristallisiert sich immer mehr heraus, dass normale Indizes nur noch zum sortierten Schmökern in Tabellen zu wie zu deren Verknüpfung benötigt werden. Für
die Suche nach Datensätzen wird hingegen immer mehr auf die Volltextindizierung zurückgegriffen.
Aus diesem Grund wurde in der Version 6.2.5 die Volltextsuche um ein Feature erweitert, dessen Erklärung etwas sophisticated, dessen Anwendung jedoch
einfach ist und das den Volltextindex nochmals aufwertet.
Die Sache dreht sich um den feldbezogenen Volltextindex. Das bedeutet: die tdbengine merkt sich in den Stichwortreferenzen nicht nur den Datensatz, indem
das Stichwort vorkommt, sondern zusätzlich auch noch das Datenfeld- gleich ob es sich dabei um Stringfelder, Memos oder irgendwelche Zahlenfelder handelt.
Wer die Dokumentation zum Volltextindex gelesen hat, weiss auch, dass es bisher durchaus möglich war, bei der Funktion Scanrecs für jedes Feld eine
Zahlenkonstante anzugeben: die sogenannte Maskenzahl (wegen bit-weiser Maskierung). Bei der Indizierung dieses Feldes wurde dann diese Zahl in die
Referenzliste mitaufgenommen:
- 105 -
Betriebssystemfunktion
ScanRecs(d,i,r,Fields("Name:1, Vorname:2, Ort:4"))
Hinweis: Damit die Maskenzahlen bei den Feldangaben übernommen werden, muss als Maskenfeld der Wert -1 angegeben werden.
Bei der Suche im Volltextindex (MarkTable oder MarkBits) muss eine Maske angegeben werden. Diese Such-Maske und die in jeder Referenz gespeicherte Zahl
werden mittels binärem AND verknüpft, und wenn dabei eine Zahl ungleich 0 herauskommt, gilt der zugehörige Datensatz als gefunden:
MarkTable(d,i,Suchwort,'','',1,r) Suchwort ist im Feld "Name" vorhanden
MarkTable(d,i,Suchwort,'','',2,r) Suchwort ist im Feld "Vorname" vorhanden
MarkTable(d,i,Suchwort,'','',4,r) Suchwort ist im Feld "Ort" vorhanden
MarkTable(d,i,Suchwort,'','',1+ Suchwort ist in wenigstens einem der Felder "Name" oder "Ort"
4,r)
vorhanden
Hinweis: 0 passt auf alle Masken, sucht also in allen Feldern
Zusätzlich gab es noch den Sonderfall, dass der gesamte Datensatz (also alle Felder) indiziert wurde:
ScanRecs(d,i,r,Fields("complete"))
Dabei verwendet die tdbengine automatische Maskenzahlen: 1 für das erste Datenfeld, 2 für das zweite, 4 für das dritte usw. Und weil die Maske in einer 32-BitZahl abgelegt wurde, konnten auf diese Art und Weise die ersten 32 Felder eines Datensatzes indiziert werden. Die restlichen Felder erhielten die Maske 0.
Hinweis: In diesem Sonderfall verwendet die tdbengine automatisch den kleinen Modus 4. Zusätzlich wird je nachdem Datentyp des Feldes der Zeichensatz
entsprechend angepasst.
Um den vielfältigen Indizierungsmöglichkeiten auch bei der Suche im Volltextindex gerecht zu werden, konnte die globale (via Parameterübergebene)
Suchmaske im Suchstring überschrieben werden:
MarkTable(d,i,'1:Kern, 2:Ulrich'...)
findet alle Datensätze, in denen das Wort Kern im Feld "Name" und das Wort "Ulrich" im Feld "Vorname" enthalten ist.
Und weil man sich die Markierungszahlen eh' nicht so gut merken kann, darf man anderen Stelle auch einfach Feldbezeichner schreiben:
MarkTable(d,i,'Name:Kern, Vorname:Ulrich'...)
- 106 -
Betriebssystemfunktion
Das funktioniert sogar mit Klammern:
MarkTable(d,i,'Name:(Klier+Kern+Schwalm)')
Und weil's gar so schön ist und noch nirgendwo dokumentiert wurde: Komplexe Suchanfragen dürfen auch in einem Ramtext stehen! Der Zeilenumbruch wird als
Leerzeichen interpretiert:
t:=rewrite("ramtext:searchexpression")
writeln(t,'Name:(Kern+Klier+G*z),')
writeln(t,'PLZ:(8*,9*)')
close(t)
...
MarkTable(d,i,"ramtext:searchexpression"...)
Und gleich noch ein Bonbon:
Bei ScanRecs dürfen in Fields nicht nur Felder der zu indizierenden Tabelle stehen, sondern auch Angaben in L-Feld- und R-Feld-Notation!
ScanRecs(d,i,r,Fields("Konto.Beschreibung,Posten.Bezeichnung…
Gewünscht...
Es hat sich nun herausgestellt, dass man in den meisten Fällen die Möglichkeiten der Bit-Operationen gar nicht ausnutzt. Manchen Anwendern würde es völlig
ausreichen, entweder in einzelnen Feldern oder im gesamten Datensatz zu suchen (und nicht in Feldkombinationen), wenn nur nicht die Suche auf die ersten 32
Felder begrenzt wäre.
...und erfüllt.
Freilich wollten wir dazu die Suchmaske nicht auf 1000 Bit aufblähen (so viele Felder verträgt die TDB derzeit). Und wenn nur in einzelnen Feldern gesucht wird,
reicht es aus, wenn die Suchmaske mit der gespeicherten Maske auf exakte Übereinstimmung geprüft wird.
Um diesen Modus anzuschalten, gibt es das neue Punktkommando: EM (=exactmatch)
.em 0
(Maske wird mit Suchmaske wie bisher mit BitAnd verknüpft, default)
.em 1
(Maske wird mit Suchmaske auf Gleichheit geprüft)
- 107 -
Betriebssystemfunktion
Wird imexact-match-Modus die Funktion ScanRecs (oder ScanRec oder UnScanRec) mit Fields ("complete") aufgerufen, so wird für jedes Datenfeld die
entsprechende Feldnummer als Maske zu den Referenzen gespeichert, es werden also alle Felder( mit Ausnahme von REL und BLOB) indiziert und mit der
entsprechenden Kennzahl versehen.
Zum Abschluss noch einTipp:
Setzen Sie nach ".em1" den Modus wieder auf den default-Wert (.em0) zurück, damit bisherige Applikationen nicht plötzlich ganz unwerwartete Ergebnisse
liefern.
Zusätzliches
Die Volltextindizierung mit fields ("complete") kann auf die ersten nDatenfelder einer Tabelle eingeschränkt werden:
fields("complete:12")
Es werden nur die ersten 12 Felder der Tabelle in den Volltextindex
übernommen
Die Syntax für die Suche im Volltextindex ist nun folgendermaßen rekursiv definiert:
Volltextsuche
Suchterm
Suchfaktor
Suchmaske
Suchwort
::=
::=
::=
::=
::=
Suchterm {(Vereinigungsoperator|Differenzoperator) Suchterm}.
Suchfaktor {Schnittoperator Suchfaktor}.
[Komplementoperator] [Suchmaske] (Suchwort|"("Volltextsuche")").
Zahlenkonstante|Feldbezeichner.
Zeichenfolge aus Buchstaben, '*', '?' und dem via Paramter übergebenen erweiterten Alphabet.
Hinweis: Wie bei numerischen Ausdrücken werden Differenzoperator und Komplementoperator durch das gleiche Zeichen ausgedrückt.
Wer die Syntaxdefinition (inEBNF) lesen kann sieht sofort, dass die Suchmaske an den Suchfaktor gekoppelt ist. Ist bei einem Suchfaktor keine Suchmakse
angegeben, wird 0 (also sich ein allen Feldern) als Defaultwert angenommen. Da ein Suchfaktor auch ein- in runden Klammern eingeschlossener-Suchausdruck
sein darf, kann die Suchmaske auch direkt vor eine Klammer gesetzt werden und gilt dann für sämtliche Bestandteile innerhalb der Klammer, wobei dort einzelne
Suchfaktoren wieder um eigene Suchmasken haben können.
Wichtig: Auch der Komplementoperator ist an den Suchfaktor gekoppelt (und bindet dem nach am stärksten). Das bedeutet, dass eine Suche auch mit diesem
Operator beginnen kann:
-Name:Müller
findet alle Datensätze, in denen im Feld Name das Wort "Müller" nicht vorkommt!
Beispiele:
- 108 -
Betriebssystemfunktion
Ausdruck
ist gleichbedeutend mit
sucht
Name:Müller + Huber
Name:Müller + 0:Huber
im Feld "Name" das Wort Müller oder
irgendwo das Wort "Huber"
Name:(Müller + Huber)
Name:Müller + Name:Huber
Im Feld "Name" das Wort "Müller" oder
"Huber" (oder beides)
Name:(Müller Westernhagen
Name:Müller Name:Westernhagen
Im Feld "Name" ist das Wort "Müller"
enthalten, nicht aber das Wort
"Westernhagen"
PLZ:(80* + 81*) Ort:München
(PLZ:80* + PLZ:81*) Ort:München
Datensätze mit den PLZs "80???" und
"81???", deren Ortsbezeichnung nicht
"München" ist
Familienstand:ledig Ort:München
Ledige, die nicht in München wohnen
-Familienstand:ledig Ort:München
-(Familienstand:ledig +
Ort:München)
alle nichtledigen Nicht-Münchner
-(Familienstand:ledig,
Ort:München)
-Familienstand:ledig + Ort:München
nicht ledig oder nicht in München (oder
beides)
Hinweis: Hier wurden die Standardoperatoren angenommen:
"-" :
"+" :
"," :
DifferenzoperatorundKomplementoperator
Vereinigungsoperator
Schnittoperator
- 109 -
Betriebssystemfunktion
12.
Der Zugriff auf Datenfelder
Grundsätzlich lässt sich der Zugriff auf die Felder einer Tabelle in zwei Arten unterteilen: den Lese- und den Schreibzugriff.
12.1
GetField und GetRField
Die beiden Funktionen GetField() und GetRField() lesen den Inhalt eines Feldes aus und geben ihn zurück.
GetField(db : INTEGER; [Feld]) : STRING
GetRField(db : INTEGER; [Feld]) : REAL
für [Feld] gilt jeweils:
Feldname : STRING
Feldnummer : INTEGER
Wenn nötig und möglich, konvertiert die tdbengine die Inhalte typgerecht um. Das heisst, beim Einsatz von GetField() auf ein Zahlen-, Datums- und Zeit- oder
auch auf ein Link-Feld wird der (numerische) Inhalt passend in einen String konvertiert. Dies ist normalerweise auch problemlos möglich.
Umgekehrt ist darauf
jedoch nur bedingt Verlass. GetRField() angewandt auf ein String-Feld führt zu einem Fehler, wenn der Inhalt des Feldes nicht konvertierbar ist, z.B. bei Texten.
Diese lassen sich nun mal nicht so einfach in eine Zahl umwandeln.
Hinweis:GetField() ist die typunabhängiste Form um an Feldinhalte zu kommen. Das Ergebnis ist immer ein String.
GetField() und GetRField() sind nicht geeignet, um die Inhalte von MEMO- bzw. BLOB-Feldern auszulesen. Stattdessen liefern diese lediglich die Texte "MEMO"
bzw. "BLOB" oder, wenn kein Inhalt vorhanden "leer" zurück. Zur Behandlung derartiger Felder lesen Sie bitte das Kapitel Memos und Blobs.
12.2
SetField und SetRField
Mit SetField() und SetRField() schreibt man Informationen in ein einzelnes Feld.
SetField(db : INTEGER; [Feld]; Wert : STRING) : STRING
SetRField(db : INTEGER; [Feld]; Wert : REAL) : REAL
für [Feld] gilt jeweils:
Feldname : STRING
- 110 -
Betriebssystemfunktion
Feldnummer : INTEGER
Zu beachten ist auch hier die Wandelbarkeit der übergebenen Informationen. In ein String-Feld kann beliebige Information geschrieben werden. Zahlen genauso
wie Texte, die tdbengine wandelt alles in einen String um.
Andersherum ist das nicht immer ohne Weiteres möglich:
SetRField(db,"Geburtstag", "Alfred") // Geburtstag ist Feld vom Typ DATE
würde schon beim Kompilieren einen Fehler verursachen, da SetRField() als 3. Parameter einen Integer-Wert erwartet.
SetField(db,"Geburtstag", "Alfred") // Geburtstag ist Feld vom Typ DATE
führt jedoch "lediglich" zu einem Laufzeitfehler, der u.U. eine ganze Weile unendeckt bleiben kann.
Diese Beispiele zeigen, wie schnell sich ein Fehler einschleichen kann. Es ist daher sehr zu Empfehlen, bei Zahlenfeldern SetRField() statt SetField() zu
verwenden, um konvertierungsprobleme schon während der Entwicklung auszumerzen.
Diese beiden Funktionen solten nicht im Zusammenhang mit MEMO- oder BLOB-Feldern verwendet werden. Über das Schreiben und Lesen derartiger Felder
lesen Sie bitte das Kapitel Memos und Blobs.
1.1
SetFields
SetFields() erlaubt das Befüllen von mehreren Feldern gleichzeitig.
SetFields(db : INTEGER; replacestr : STRING) : INTEGER
replacestr ist eine, durch Komma miteinander verknüpfte, Kette von Key/Value-Paaren.
feld1=wert1, feld2=wert2, ... feldn=wertn
Nachdem die tdbengine lediglich Strings mit einer maximalen Länge von 255 Zeichen verwalten kann, ist der Gebrauch dieser Funktion stark eingeschränkt
Das
Funktionsergebnis ist im Erfolgsfall 0, ansonsten die entsprechende Fehlernummer.
IFSetFields(db, "Name='"+cName+"',Groesse="+Str(nSize)+",ID='"+cID+"'") <> 0 THEN
//Fehler beim Speichern
END
- 111 -
Betriebssystemfunktion
Der Einsatz der Funktion SetFields() ist nicht sonderlich empfehlenswert, da jegliche Fehlerprüfung erst zur Laufzeit durchgeführt werden kann. Am Besten ist es,
mehrere SetField() und SetRField() -Anweisungen nacheinander zu benutzen.
- 112 -
Betriebssystemfunktion
13.
Übersicht über alle Tabellenfunktionen
Funktion
access()
andmarks()
autofield()
autorecno()
blobsize()
blobtype()
cleardat()
closedb()
copyblob()
copymemo()
dbdir()
dbname()
dbno()
dbrights()
deldb()
delindex()
delmark()
delmarkedrecords()
delmarks()
delrec()
editoff()
editon()
embedblob()
exists()
fields()
filemode()
fileno()
filesize()
findandmark()
findauto()
Beschreibung
Legt die Zugriffsreihenfolge für eine Tabelle fest
Bildet den Mengendurchschnitt zwischen Markierungsliste und Array
Liefert die Feldnummer des Feldes, das die Autonummer speichert
Liefert die Autonummer des aktuellen Datensatzs
Liefert die Größe eines BLOBs
Liefert den Typ des Inhalts eines BLOB-Feldes
Löscht alle Datensätze einer Datei
Schliesst eine Tabelle
Kopiert den (eingebetteten) Inhalt eines BLOB-Feldes in eine Datei oder ein Array
Kopiert ein Memo in eine (externe) Textdatei
Verzeichnisteil des Pfades einer Tabelle
Namensteil des Pfades einer Tabelle
Rechte an geöffneter Tabelle
Löscht eine Tabelle inklusive aller dazugehörenden Elemente
Löscht einen bestehenen Index und entfernt ihn aus der Tabelle
Löscht eine Satznummer aus der Markierungsliste einer Tabelle
Löscht alle Datensätze aus der Markierungsliste
Löscht die gesamte Markierungsliste einer Tabelle
Löscht einen Datensatz physikalisch aus einer Tabelle
Sperre freigeben
Satzsperre einrichten
Liest eine externe Datei oder eine Feldvariable in ein BLOB-Feld
Prüft die Erfüllbarkeit einer Bedingung
Feld-Definition für Volltextindizierung
Liefert die Zugriffsrechte an einer Tabelle (veraltet)
Liefert Handle der aktuellen Primärtabelle
Anzahl der Datensätze einer Tabelle
markiert eine Auswahl (Selektion)
Liefert die physikalische Satznummer zu einer AUTO-Nummer
- 113 -
Betriebssystemfunktion
findrec()
firstmark()
firstrec()
flushdb()
fsum()
genindex()
getcode()
getdef()
getfield()
getmarks()
getmode()
getpw()
getrec()
getrfield()
getstructure()
gettype()
incrfield()
inddef()
indname()
indno()
ismark()
label()
labelno()
lastrec()
link()
linkblob()
linkinfo()
lock()
makedb()
markdoubles()
markindex()
markpattern()
maxfile()
maxlabel()
memolen()
memostr()
Sucht einen Datensatz über einen Index
Liefert die physikalische Satznummer des Anfangs der Markierungsliste einer Tabelle
Liefert die Nummer des ersten Datensatzes bzgl. aktuellem Zugriff
Leert den Schreibpuffer
Addiert die Feldinhalte aufeinanderfolgender Datenfelder
Erzeugt einen neuen Index
Verschlüsselungscode einer Tabelle
Schreibt die Struktur einer Tabelle in eine Textdatei
liefert den Inhalt eines eines Feldes aus dem Satzpuffer
speichert eine Markierungsliste
Tabellenrechte ermitteln (veraltet)
Passwort einer Tabelle ermitteln
Schreibt den Satzpuffer in ein Array
liefert den Inhalt eines eines Feldes aus dem Satzpuffer
Struktur eines Tabellen-Feldes ermitteln
Feldtyp ermitteln (veraltet)
Incrementiert den Wert eines Datenfeldes
Indexbeschreibung ermitteln
Liefert den Namen eines Index
Nummer des aktiven Index ermitteln (veraltet)
Datensatz in Markierungsliste prüfen
Feldbezeichner ermitteln
Feldnummer ermitteln
Nummer des letzten Datensatzes bzgl aktuellem Zugriff
verknüpft eine externe Datei mit einem BLOB-Feld
Tabellensperre einrichten
Anslage einer neuen Tabelle
Markiert doppelte Einträge bzgl. eines Index
Sortiert die Markierungsliste nach einem Index
größter gültiger Tabellenhandle
Anzahl der Datenfelder (Spalten) in einer Tabelle
Anzahl der Zeichen in einem Memo-Feld ermitteln
Anfang eines Memo-Feldes als String
- 114 -
Betriebssystemfunktion
newtable()
nextmark()
nextrec()
nmarks()
opendb()
prevrec()
primfile()
primtable()
putmarks()
putrec()
readmemo()
readrec()
recno()
recno()
recnr()
regenall()
regenind()
relation()
rendb()
reopendb()
revmarks()
setalias()
setauto()
setfield()
setfields()
setfilter()
setmark()
setmarks()
setrecord()
setrfield()
sortmark()
treeinfo()
unlock()
writerec()
Kopie (leer) einer Tabelle erzeugen
nächster markierter Datensatz
Physikalische Satznummer des Nachfolgers
Größe der Markierungsliste eines Tabellenhandle
Öffnen einer Tabelle
Physikalische Satznummer des Vorgängers
Setzt die Primärtabelle (veraltet)
Setzt die Primärtabelle
Setzen der Markierungsliste einer Tabelle
Schreibt ein Array in den Satzpuffer
Liest Memo aus Textdatei
Liest einen Datensatz
Nummer des aktuellen Datensatzes einer Tabelle
Physikaliche Satznummer des aktuellen Datensatzes
Nummer des aktuellen Datensatzes einer Tabelle (veraltet)
Regeneriert sämtliche Indizes einer Tabelle
Regeneriert den Index einer Tabelle
Gibt einer Tabelle einen neuen Dateinamen
Öffnet eine bereits geöffnete Tabelle mit anderen Rechten
Dreht die Ordnung der Markierungsliste einer Tabelle um
Erzeugt Alias-Handle zu bereits geöffneter Tabelle
Setzen der nächsten AUTO-Nummer
Übertragung von Informationen in den Satzpuffer
mehrere Felder eines Datensatzes belegen
Setzt einen Filter auf den aktiven Index
Setzt eine Marke in der Markierungsliste
Markiert alle Datensätze einer Tabelle
Kopiert einen Datensatz aus einer anderen Tabelle
Übertragung von numerischen Informationen in den Satzpuffer
sortiert die Markierungsliste
Index-Informationen
Sperre freigeben
Schreibt einen Datensatz
- 115 -
Betriebssystemfunktion
14.
Der Zugriff auf Memofelder – 18.11.2011
14.1
Memo einlesen – ReadMemo()
Sowohl Memos als auch Blobs werden in eigenen physikalischen Dateien gespeichert. In der zugehörigen Tabelle werden nur Zeiger auf den entsprechenden
Eintrag in der jeweiligen Memo- oder Blob-Datei hinterlegt. Ein Memo-Feld speichert einen beliebig großen Text, ein Blob-Feld (BLOB = Binary Large OBject)
irgendwelche Binärdaten.
Bei der einzulesenden Textdatei kann es sich um einen internen (Ramtext) oder einen externen Text handeln.
Wichtig: ReadMemo() kann nur auf bestehende Datensätze angewandt werden, der Satzzeiger muss also einen Wert > 0 haben.
Bei der Ausführung von ReadMemo() wird zunächst die Textdatei in die Memodatei übertragen.
Dann wird die Referenz (in die Memodatei) in das entsprechende Datenfeld übertragen und schließlich der Datensatz in die Tabelle zurückgeschrieben.
Diese Transaktion sollte niemals ohne Dateisperre ausgeführt werden.
Wenn man ein Memofeld in einen neuen Datensatz einlesen will, muss dieser vor(!) dem Aufruf von ReadMemo() in die Tabelle geschrieben werden:
Hinweis: Der Inhalt eines Formularfeldes (HTML), dessen Name mit "text:..." beginnt, wird in einem Ramtext mit dem Namen "ramtext:text:..." bereitgestellt. Der
Zeichensatz in diesem Ramtext ist ASCII.
Wichtig:
ReadMemo arbeitet immer mit dem aktuellen (also zuletzt gelesenen oder geschriebenen) Datensatz. Es wird immer eine komplette Transaktion durchgeführt,
also inklusive dem Zurückschreiben des aktuellen Datensatzes. Deshalb ist ein abschließendes WriteRec() nicht nötig.
14.1.1
Syntax
READMEMO(DB : INTEGER; Feld; Textdatei : STRING [; Mode : INTEGER [; Charset : INTEGER]]) : INTEGER
Parameter
DB : Tabellenhandle von OPENDB
Feld: Feldnummer oder Feldbezeichner (als String)
Textdatei : Pfad zur Textdatei
Mode :
0 : Textdatei wird an Memo angehängt (default)
1 : Textdatei ersetzt bestehenden Memo
CharSet :
0 : ANSI (default)
1 : ASCII
14.1.2
Ergebnis
0 : Operation erfolgreich
sonst Fehlercode
- 116 -
Betriebssystemfunktion
Mit einem zusätzlichen Parameter kann festgelegt werden, ob durch den Aufruf der Funktion ein bereits bestehendes Memo durch die Textdatei ersetzt oder
ergänzt wird:
14.1.3
Beispiele
ReadMemo(db,'Memofeld',pfad_zur_textdatei,0)
Text wird an bestehendes Memo angehängt
ReadMemo(db,'Memofeld',pfad_zur_textdatei,1)
Text ersetzt bestehendes Memo
Der Vorgabewert (also wenn nichts angegegeben wird) ist 0.
Ein weiterer Parameter legt fest, ob der externe Text im ANSI- oder in ASCII-Zeichensatz vorliegt:
0 (Vorgabe) ANSI-Zeichensatz
1 ASCII-Zeichensatz
ReadMemo(db,'Memofeld',pfad_zur_textdatei,1,1)
die externe Textdatei liegt im ASCII-Zeichensatz vor und ersetzt das bisherige Memo
ReadMemo(db,'Memofeld',pfad_zur_textdatei,0,1)
die externe Textdatei liegt im ASCII-Zeichensatz vor und wird an das bisherige Memo angehängt
ReadRec(DB,0) // prepare new record
SetField(DB,'Name',CGIGetParam('Name'))
.. // fill other fields
WriteRec(DB,FileSize(DB)+1) // write record to table
ReadMemo(DB,'Notes','ramtext:text:notes',1,1) // read in memo
Der Rückgabewert von ReadMemo() ist 0, wenn alles in Ordnung ist, ansonsten wird der Fehlercode geliefert.
14.2
Memogröße bestimmen – MemoLen()
Die aktuelle Größe (in Bytes) eines Memos liefert die Funktion MemoLen()
MemoLen(dn,'Memofeld') -> Anzahl aller Zeichen des zugehörigen Textes.
- 117 -
Betriebssystemfunktion
14.3
MEMOS AUS HTML-FORMUAREN EINLESEN
In HTML-Formularen werden Memos normalerweise über ein TEXTAREA-Tag eingelesen. Die Textarea sollte einen Namen haben, der mit "text:" beginnt:
<textarea name="text:Bemerkung" ...
Das Formular mit der Textarea muss die Methode "POST" verwenden. Dann wird beim Ausführen des entsprechenden Programms automatisch ein Ramtext
angelegt, das den Inhalt der Textarea im ASCII-Zeichensatz enthält. Wenn Sie also die Textarea in ein Memo einlesen wollen, genügt folgender Aufruf:
ReadMemo(db,'Memofeld','ramtext:text:Bemerkung',1,1)
14.4
Memo auslesen – CopyMemo()
Das Gegenstück von ReadMemo() steht mit der Funktion CopyMemo() zur Verfügung, mit der der Inhalt eines Memofeldes in eine (interne oder externe)
Textdatei übertragen wird.
CopyMemo(db,'Memofeld',pfad_zur_textdatei)
Auch hier gibt es einen weiteren Parameter, der den Zeichensatz der Textdatei festlegt:
0 (Vorgabe) -> ANSI-Zeichensatz
1 -> ASCII-Zeichensatz
Wie ReadMemo() arbeitet auch CopyMemo() mit dem aktuellen Datensatz.
14.5
MEMOS IN TEMPLATES
Die Funktion Subst() bietet eine sehr bequeme Möglichkeit, Memos in Templates einzufügen:
Subst(target,db,'Memofeld',1)
fügt an die Stelle des Targets den Inhalt des Memofeldes ein, wobei bereits eine korrekte Zeichenkonvertierung nach HTML vorgenommen wird. Diese Methode
wird eingesetzt, wenn das Target den Inhalt eines Textareas definiert, das Memo also editiert werden soll.
Ausserhalb eines Textareas wird man
Subst(target,db,'Memofeld',5)
wählen, denn hierbei werden harte Zeilenumbrüche innerhalb des Memos durch '<br>' ersetzt.
- 118 -
Betriebssystemfunktion
Tipp: Wenn Sie in Memos bestimmte Auszeichnungstags (Beispiel <b>..</b>) einsetzen, können Sie diese auch noch nach der HTML-Konvertierung wirksam
werden lassen:
WHILESubst('<b>','<b>') DOEND
WHILESubst('</b>','<b>') DOEND
14.6
SUCHE IN MEMOS
Über eine sequentielle Suche kann in Memos mit den Operatoren has bzw. like wie in Stringfeldern gesucht werden.
Memofelder können auch in einen Volltextindex aufgenommen werden, was zu einer sehr schnellen Suche führt.
- 119 -
Betriebssystemfunktion
15.
Speichern von binären Daten in Blob Dateien
15.1
blobsize() - Liefert die Größe eines BLOBs
Beschreibung
Wenn das BLOB-Feld leer ist, wird 0 zurückgegeben. andernfalls der in der BLB-Datei belegte Platz.
Die Prozedur liefert die Größe des BLOB-Feldes Sample, unabhängig davon ob es sich um ein verknüpftes oder ein eingebettetes BLOB handelt:
PROCEDURE GetBlobSize: INTEGER;
VAR BlobPath, s: String;
BlobPath := GetLinkedFile(Sample);
IF Length(BlobPath) = 0 THEN
RETURN BlobSize(Sample);
ELSE
RETURUN GetSize(BlobPath)
END
ENDPROC
Syntax
BLOBSIZE(fielddesc) : INTEGER
Parameter
fielddesc :
Feldbezeichner eines BLOB-Feldes (oder)
Tabellenhandle, Feldnummer (oder)
Tabellenhandle, Feldbezeichner
Ergebnis
Belegter Speicherplatz des BLOB-Feldes
15.2
blobtype() - Liefert den Typ des Inhalts eines BLOB-Feldes. (nicht mehr relevant)
Beschreibung
Liefert den Typ des Inhalt des BLOB-Feldes, wie er bei LINKBLOB oder EMBEDBLOB festgelegt wurde.
Syntax
BLOBTYPE(fielddesc) Parameter fielddesc :
- 120 -
Betriebssystemfunktion
Feldbezeichner eines BLOB-Feldes (oder)
Tabellenhandle, Feldnummer (oder)
Tabellenhandle, Feldbezeichner Ergebnis Typ des Blob-Feldes
Vorgabe:
15.3
copyblob() - Kopiert den (eingebetteten) Inhalt eines BLOB-Feldes in eine Datei oder ein Array
Syntax
COPYBLOB(blob : filddesc; [file : STRING|VAR array : [BYTE|CHAR|INTEGER|TBITS| STRING]]) : INTEGER
Parameter
blob : Tabellenhandle,Feldnummer (oder)
Tabellenhandle,Feldbezeichner
file : Pfad zur Datei, in der die Kopie gespeichert wird
array : Feldvariable beliebigen Typs
Ergebnis
Fehlercode
0 : erfolgreich übertragen
Beschreibung
COPYBLOB kann nur bei eingebetteten BLOBs verwendet werden.
Bei Übergabe einer Feldvariablen wird der Inhalt des BLOBs in diese übertragen. Die Initialisierung des Arrays erfolgt nicht automatisch.
Es wird empfohlen, die Initialisierung in jedem Fall vorzunehmen.
Hier ein Beispiel für die Markierung von Datensätzen, welche in einem Blob als Integer Array gespeichert wurden.
readAuto(DG,id)
InitArray(ai[FileSize(I)])
CopyBLOB(DG,"ICD",ai)
PutMarksAuto(I,ai)
InitArray(ai[NMarks(I)])
GetMarksAuto(I,ai)
Beachten Sie, dass beim Speichern ins BLOB und beim Wiederauslesen jeweils ein Array vom identischen Typ übergeben werden muss, da sonst "Datenmüll"
entsteht.
Es ergeben sich dabei sehr interessante Möglichkeiten im Zusammenspiel mit Markierungslisten.
- 121 -
Betriebssystemfunktion
Wird bei der Zieldatei 'con' angegeben, so wird das BLOB auf die Standardausgabe ausgegeben.In diesem Fall wird das BLOB an die Standardausgabe
ausgegeben (also direkt an den CGI-Klienten).
VAR t : INTEGER
Beispiel zu CopyBLOB(db,'BlobFeld','con'):
IF t:=BLOBType(db,'Bild')=5
THEN CGIWriteLn('content-type: image/gif')
ELSIF t=6
THEN CGIWriteLn('content-type: image/jpeg')
END
CGIWriteLn('')
CGICloseBuffer
CopyBLOB(db,'Bild','con')
15.4
Blob einlesen – EmbedBlob()
Wie Memos werden auch BLOBs in einer eigenen Datei abgelegt. In der Tabelle selbst wird nur ein Zeiger auf den Anfang angelegt. Bei BLOBs werden jedoch
zwei Arten der Einbindung unterschieden:
Eine externe Datei wird komplett in die BLOB-Datei kopiert (embedded)
Es wird nur eine Referenz auf die externe Datei gespeichert (link)
Für jeden Fall gibt es eine eigene Funktion:
EmbedBlob(db,'Blobfeld',pfad_zur_datei)
speichert die gesamte Datei in das BLB-File
LinkBlob(db,'Blobfeld',pfad_zur_datei)
speicher nur den pfad_zur_datei in das BLB-File
EmbedBLOB(db,'Document','xy.prg ')
Das Funktionsergebnis beider Funktionen ist 0, wenn die Operation erfolgreich war, ansonsten wird der Fehlercode zurückgeliefert.
Wenn LinkBlob() ohne Dateinamen aufgerufen wird, liefert die Funktion den aktuell gespeicherten Pfad (Referenz).
Gelinkte BLOBs sind im Zusammenhang mit der tdbengine relativ langweilig, weil für eine Referenz zu einer einer externen Datei ein normales STRING-Feld
ausreicht und der Aufwand für eine zusätzliche Datei zu groß ist. Zudem merkt die Datenbank nichts davon, wenn eine Referenz ins Leere zeigt, weil die
zugehörige Datei gelöscht, umbenannt oder in ein anderes Verzeichnis verschoben wurde.
- 122 -
Betriebssystemfunktion
Anders liegt der Fall bei eingebetteten BLOBs, denn hierbei liegt eine komplette Kopie vor, die Ursprungsdatei wird demnach nicht weiter benötigt.
15.5
Wichtige Hinweise zur Verwendung von blob-Feldern
Sie sollten weder Memos noch Blobs mit SetField() eine Wert zuweisen.
Sie können mit SetField(db,'BlobFeld','') ein Blobfeld löschen. Allerdings bleibt dabei die eingebundene Datei in der BLOB-Datei enthalten, es wird nur der
Verweis gelöscht.
Sorgen Sie dafür, dass ausser dieser 0-Zuweisung keine weiteren direkten Zuweisungen an Memo- oder Blob-Felder erfolgen. Andernfalls kann die gesamte
Verwaltung so durcheinander kommen, dass die Tabelle nicht mehr brauchbar ist.
Eine Falle im obigen Sinne besteht in folgenden Programmfragment:
ReadRec(db,x); WriteRec(db,FileSize(x)+1)
Hier wird eine Kopie des Datensatzes angelegt, dabei werden auch die Memo- und Blob-Refernzen kopiert (was schnell zur Katastrophe führt).
Richtig wäre:
ReadRec(db,x); SetRecord(db,db); WriteRec(db,FileSize(x)+1)
Haben Sie das Gefühl, dass bei Memos oder Blobs irgendetwas nicht stimmt (weil beispielsweise die Änderungen in einem Memofeld plötzlich auch in einem
anderen auftauchen), so restrukturieren Sie die Tabelle mit dem baseman.
Dabei werden immer neue konstistente Memo- und Blob-Dateien angelegt.
- 123 -
Betriebssystemfunktion
16.
Datensätze markieren
16.1
setmark() - Setzt eine Marke in der Markierungsliste
Beschreibung
Wenn x ausserhalb des gültigen Bereiches (1..FileSize(db)) liegt, wird ein Laufzeitfehler ausgelöst.
Wenn x bereits in der Markierungsliste enthalten ist, findet keine weitere Aktion statt. Andernfalls wird x am Ende der Markierungsliste angehängt.
Syntax
SETMARK(db : INTEGER; x : INTEGER) : INTEGER
Parameter
db : Tabellenhandle von OpenDB
x : physikalische Satznummer
Ergebnis
x
16.2
setmarks() - Markiert alle Datensätze einer Tabelle
Beschreibung
Mit Setmarks werden alle Datensätze einer Tabelle markiert. Vorher sollte man unbedingt mit delMarks sicherstellen, dass die Markierungsliste vor dem Aufruf
leer ist.
Andernfalls werden in der Markierungsliste mehr Einträge hinterlegt als es Datensätze gibt.
Syntax
SetMarks(db : INTEGER)
Parameter
db : Tabellenhandle
- 124 -
Betriebssystemfunktion
16.3
delmark() - Löscht eine Satznummer aus der Markierungsliste einer Tabelle
Beschreibung
Der Datensatz mit der physikalischen Satznumer recno wird aus der Markierungsliste der Tabelle entfernt.
Falls der Datensatz nicht in der Markierungsliste ist, wird kein Fehler erzeugt.
Syntax
DELMARK(db : INTEGER; recno : INTEGER) : INTEGER
Parameter
db : Tabellenhandle von OPENDB
recno : physikalische Satznummer
Ergebnis
Satzummer des aus der Markierungsliste gelöschten Datensatzes (also recno)
16.4
revmarks() - Dreht die Ordnung der Markierungsliste einer Tabelle um
Syntax
REVMARKS(db : INTEGER) : INTEGER
Parameter
db : Tabellenhandle von OPENDB
Ergebnis
Anzahl der Elemente in der Markierungsliste.
16.5
delmarkedrecords() - Löscht alle Datensätze aus der Markierungsliste.
Beschreibung
Tritt während des Löschens ein Fehler auf, so wird ein Laufzeitfehler ausgelöst und das Programm abgebrochen.
Der Laufzeitfehler kann mit SetPara('ec 1') abgefangen werden.
Syntax
DELMARKEDRECORDS(db : INTEGER) : INTEGER
Parameter
- 125 -
Betriebssystemfunktion
db : Tabellenhandle von OpenDB
Ergebnis
Anzahl der erfolgreich gelöschten Datensätze
- 126 -
Betriebssystemfunktion
16.6
delmarks() - Löscht die gesamte Markierungsliste einer Tabelle
Beschreibung
Die gesamte Markierungsliste einer Tabelle wird gelöscht.
Wie bei allen Tabellenfunktionen wird beim Zugriff auf einen ilegalen Tabellenhandle ein Laufzeitfehler ausgelöst.
Syntax
DELMARKS(db : INTEGER) : INTEGER
Parameter
db : Tabellenhandle von OPENDB
Ergebnis
1
16.7
ismark() - Datensatz in Markierungsliste prüfen
Beschreibung
ISMARK prüft jedesmal die Markierungsliste von db. Wenn sehr viele Datensätze geprüft werden müssen, empfielt es sich, die Markierungsliste auf ein TBITSArray abzubilden und dort die Prüfung vorzunehmen, da diese Vorgehensweise wesentlich schneller ist:
VAR marks : TBITS[]
...
InitArray(marks[filesize(db)]);
GetMarks(db,marks)
IF marks[RecNo] THEN ...
Syntax
ISMARK(db, RecNo : INTEGER) : 0|1
Parameter db :
Tabellenhandle von OpenDB
RecNo : physikalische Satznummer
Ergebnis
1 : Wenn RecNo in Markierungsliste von db
0 : andernfalls
- 127 -
Betriebssystemfunktion
16.8
findandmark() - markiert eine Auswahl (Selektion)
Beschreibung
In die Markierungsliste des Tabellenhandles werden alle Satznummern aufgenommen, die die Selektion erfüllen.
Multiexpression ist ein String der Form "Ausdruck; Ausdruck; ...". Die einzelnen Ausdrücke werden für jeden gefundenen Datensatz
berechnet. Sowohl in der Selektion als auch in Multiexpression kann die Feldnotation [Tabellenname.]Feld verwendet werden.
Syntax
FINDANDMARK(db : INTEGER; Selektion : STRING [;Multiexpression : STRING]) : INTEGER
Parameter
db : Tabellenhandle von OpenDB
Selektion : Selektion in STRING-Form
Multiexpression : String, der beliebige Ausdrücke enthalten kann (durch ";" getrennt)
Ergebnis
<0 Fehler
>=0 Anzahl der markierten Datensätze
Beispiel:
VAR x : REAL
FINDANDMARK(db,'Gehalt>250000','x:=MEAN(Gehalt)')
Damit wird die Tabelle (mit dem Handle db) nach allen Datensätzen durchsucht, bei denen das Feld 'Gehalt' einen höheren Wert als
250000 aufweist. Nach der Ausführung der Funktion sind die gesuchten Datensätze in der Markierungsliste und x hat als Wert den
Durchschnittswert des Feldes 'Gehalt' dieser Datensätze.
Der Rückgabewert der Funktion ist negativ, wenn ein Fehler auftrat (in der Selektion oder im Multiexpression).
Anderfalls wird die Anzahl der gefundenen Datensätze zurückgeliefert (also NMARKS(db)).
Hinweis:
Man kann bei FindAndMark auch bereits belegte Variablen für die Suchanweisung benutzten
So ist folgende Syntax möglich
Var id=4711
FindAndMark(Z,"$AutoID_Variante=id”)
- 128 -
Betriebssystemfunktion
oder
FindAndMark(Z,"$AutoID_Variante=”+str(id))
Wenn sich aber der Inhalt der Variablen id ändert, so muss man die 2. Variante wählen.
16.9
markdoubles() - Markiert doppelte Einträge bzgl. eines Index
Beschreibung
Markiert alle, über den in index angegebenen Index, mehrfach identischen Einträge.
Im Modus mode=0 werden bei n identischen Einträgen genau n - 1 Datensätze markiert. Der erste Datensatz (soz. der Master-Datensatz) wird nicht markiert.
Ist der Modus mode=1, dann wird auch der erste Datensatz, also genau n Datensätze, markiert.
Syntax
MARKDOUBLES(db : INTEGER; index : INTEGER|STRING [,mode : INTEGER]) : INTEGER
Parameter
db : Tabellenhandle von OpenDB
index : Indexnummer oder Indexname
mode : 0 : nur Doppelte
mode : 1 = Doppelte und erste Datensätze
Ergebnis
Anzahl der gefundenen Datensätze.
Zusätzlich sind die doppelten Einträge markiert.
16.10 markindex() - Sortiert die Markierungsliste nach einem Index
Beschreibung
MarkIndex erlaubt es bei sehr grossen Datenmengen, alle markierten Datensätze anhand der Informationen aus dem Index neu zu sortieren. Die Sortierordnung
wird durch den Index festvorgegeben und ist somit unflexibler als bei SortMark(). Dafür erhält man ein Plus an Performance.
Syntax
MarkIndex(tabelle : INTEGER; index : STRING)
Parameter
tabelle : von OpenDB geliefertes Tabellen-Handle
index : Name eines zur Tabelle gehörenden Index
- 129 -
Betriebssystemfunktion
16.11
markpattern()
Syntax
MARKPATTERN(db,index,pattern) : INTEGER
Parameter
db : TableHandle
index : Indexnummer/-name
pattern: Muster mit Wildcards
- 130 -
Betriebssystemfunktion
Ergebnis
-1 : ungültiger Index
-2 : Index kann nicht geöffnet werden
-3 : Erste Komponente des Index ist kein String
>=0 : Anzahl der gefundenen Datensätze
16.12 andmarks() - Bildet den Mengendurchschnitt zwischen Markierungsliste und Array
Beschreibung
Nach dieser Operation sind nur noch diejenigen Datensätze der Tabelle markiert, die vorher markiert waren und deren Satznummern in dem Array enthalten
sind.
Ihre Anzahl wird zurückgegeben.
Syntax.
ANDMARKS(Tabelle: INTEGER; Markierungen: Array[]): Integer
Parameter
Tabelle: Tabellenhandle, wie von OPENDB geliefert.
Markierungen : INTEGER|TBIT-Array mit physikalischen Satznummern von der Tabelle
Ergebnis
Größe der Markierungsliste nach der Operation
Beispiel
VAR MarkList: TBit[100]
// zweiten und fünften Eintrag der Kunden-Tabelle markieren
SetMark(KUNDEN, 2)
SetMark(KUNDEN, 5)
// Markierungen merken
GetMarks(KUNDEN, MarkList)
DelMarks(KUNDEN) // aktive Markierungen löschen
// den ersten und fünften Datensatz markieren
SetMark(KUNDEN, 1)
SetMark(KUNDEN, 5)
// logische Verknüpfung der aktiven und gemerkten Markierungen durchführen
AndMarks(KUNDEN, MarkList)
- 131 -
Betriebssystemfunktion
// Jetzt ist nur noch der fünfte Datensatz markiert
- 132 -
Betriebssystemfunktion
16.13
firstmark[() - Liefert die physikalische Satznummer des Anfangs der Markierungsliste einer Tabelle
Syntax
FIRSTMARK(Tabellenhandle : INTEGER) : INTEGER
Parameter
Tabellenhandle von OPENDB
Ergebnis
Physikalische Satznummer des Kopfes der Markierungsliste.
0, wenn Markierungsliste leer
- 133 -
Betriebssystemfunktion
17.
Mathematische Funktionen
17.1
Native Funktionen
17.1.1
Quadratwuzel –SQRT()
Ist x<0, so wird ein Laufzeitfehler ausgelöst.
Syntax
SQRT(x:REAL):REAL
Parameter
x:REAL>=0
Ergebnis
Quadratwurzel von x
17.1.2
Absolutbetrag –ABS()
Liefert den Absolutbetrag eines numerischen Wertes
Syntax
ABS(NV:REAL):REAL
Parameter
NV:numerischer Ausdruck
Ergebnis
NV, für NV>=0
-NV, für NV<0
Beispiel: Unterscheidet sich das Gehalt vom Durchschnitt?
VAR mean_value, salary, threshold : REAL
...
IF ABS(mean_value-salary)>threshold THEN
// unjust salary
END
- 134 -
Betriebssystemfunktion
17.1.3
Arkuskosinus einer Zahl - ARCCOS()
Syntax
ARCCOS(x : REAL):REAL
Parameter
x:beliebige Zahl
Ergebnis
Arkuskosinus
17.1.4
Kosinus einer Zahl – COS()
Syntax
COS(x : REAL) : REAL
Parameter
x: beliebige Zahl
Ergebnis
Kosinus (Winkelfunktion)
17.1.5
Exponential-Funktion - EXP()
Syntax
EXP(x:REAL):REAL
Parameter
x:beliebige Zahl
Ergebnis
e hoch x
- 135 -
Betriebssystemfunktion
17.1.6
Gebrochener Anteil einer Zahl - FRAC()
x=INT(x)+FRAC(x)
Syntax
FRAC(x:REAL):REAL
Parameter
x:REAL
Ergebnis
gebrochener Anteil von x
17.1.7
Ganzzahliger Anteil einer Zahl – INT()
Syntax
INT(x : REAL):REAL
Parameter
x:REAL
Ergebnis
ganzzahliger Anteil von x
INT(15.5):15
INT(-15.5):-15
- 136 -
Betriebssystemfunktion
17.1.8
natürlicher Logaritmus - LOG()
Wenn x<=0 ist, so wird ein Laufzeitfehler ausgelöst.
Syntax
LOG(x:REAL):REAL
Parameter
x:REAL>0
Ergebnis
natürlicher Logarithmus (zur Basis e)
17.1.9
Zufallszahl – RANDOM()
Der Zufallsgenerator der tdbengine liefert statistisch recht gut gleichverteilte ganze Zahlen.
Die Initialisierung erfolgt mit Systemwerten wie z.B. der Uhrzeit (Systemzeit).
Deshalb ist die Reihenfolge der Zufallszahlen in der Regel nicht reproduzierbar.
Syntax
RANDOM(n : INTEGER) : INTEGER
Parameter
n : positive ganze Zahl
Ergebnis
Zufallszahl aus dem Bereich 0 bis n-1
Beispiel: 10 Prozent der Datensätze in DB werden zufällig markiert
VAR test : TBITS[]
InitArray(test[FileSize(DB)])
WHILE NBits(test)<0.10*FileSize(DB) DO
test[1+RANDOM(FileSize(DB))]:=1
END
PutMarks(DB,test)
//now 10% of table are marked
- 137 -
Betriebssystemfunktion
Rundet eine Zahl kaufmännisch auf n Stellen hinter dem Komma – ROUND()
17.1.10
Syntax
ROUND(x:REAL;n:INTEGER):REAL
Parameter
x:zu rundende Zahl
n:gewünschte Stellenzahl
Ergebnis
x nach der kaufmännischen Rundung auf n Stellen nach dem Komma
Beispiel:
Funktion
Ergebnis
Round(100.234567,1)
100.2
Round(3.14159,3)
3.142
Round(0.5,1)
0.5
17.1.11
Sinus – SIN()
Syntax
SIN(x:REAL):REAL
Parameter
x:REAL (Winkel im Bogenmaß)
Ergebnis
Sinus von x
- 138 -
Betriebssystemfunktion
17.1.12
Zahl in String konvertieren – STR()
Um eine Zahl in eine Zeichenette zu konvertieren, gibt es die Funktion STR
STR(x) Zahl als Zeichenkette (auf ganze Zahl gerundet)
STR(x,s) Wie oben, aber mit insgesamt s Zeichen
STR(x,s,n) Wie zuvor aber mit n Nachkommastellen
STR(x,s,n,t) Wie zuvor und mit Tausendertrennungzeichen t
STR(x,s,n,t,f) Wie zuvor, aber linke Leerstellen werden durch das Füllzeichen f ersetzt
STR(x,s,n,t,f,k) Wie oben, aber statt Dezimalkomma wird das Zeichen k eingesetzt
Beispiele:
Funktion
Ergebnis
STR(123.456)
"123"
STR(123.456,10)
"
123"
STR(123.456,10,2)
"
123,46"
STR(123456,1,0,'.')
"123.456"
STR(123456,1,2,'.')
"123.456,00"
STR(123456,13,2,' ','_','.')
"___123 456.00"
Syntax
STR(x:REAL[;s:INTEGER[;n:INTEGER[;t:STRING[;f:STRING[;k:STRING]]]]]]:STRING
Parameter
x:beliebiger REAL-Ausdruck
s:Anzahl Zeichen, mit denen x mindestens ausgegeben wird (links aufgefüllt mit Leereichen)
n:Anzahl Nachkommastellen (Vorgabe 0)
t:Zeichen für Tausendertrennung (Vorgabe keines)
f:linkes Füllzeichen (statt Leerzeichen)
k:Komma-Zeichen (Vorgabe '.')
Ergebnis
Zahl als String
- 139 -
Betriebssystemfunktion
17.1.13
Testet, ob ein Bit gesetzt ist – TESTBIT() – 21.03.2013
Syntax
TESTBIT(x,n:INTEGER):INTEGER
Parameter
x:ganze Zahl
n:0..31(in 32-Bit-Version)
0..63(in 64-Bit-Version)
Ergebnis
1:das Bit n ist gesetzt
0:das Bit n ist nicht gesetzt
Die nachfolgende Tabelle zeigt die Ergebnis von Testbit die ersten 40 Werte.
Die 1. Zeile zeigt an, welcher Wert als 1. Parameter an TestBit übergeben wird.
Die 1. Spalte zeigt an, welcher Wert als 2. Parameter an TestBit übergeben wird.
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
001010101010 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
100110011001 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 0
200001111000 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0
300000000111 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1
400000000000 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0
500000000000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1
600000000000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- 140 -
Betriebssystemfunktion
Hierzu das erzeugende Programm:
Uses ../module/tdb7basic.mod
Uses ../module/xhr_basic.mod
Procedure Main
Var f : Integer = Rewrite("ramtext",0)
Var i,i2 : Integer
i:=1
Write(f,'Testbit Funktion<br>')
Write(f,'Die 1. Zeile zeigt an, welcher Wert als 1. Parameter bei TestBit übergeben wird.<br>')
Write(f,'Die 1. Spalte zeigt an, gegen welchen Wert geprüft wird<br>')
Write(f,'<table border="1">')
Write(f,'<tr><td></td>')
i:= 0
Repeat
Write(f,'<th>'+Str(i)+'</th>')
Until i++>40
Write(f,'</tr>')
i2:=0
Repeat
Write(f,'<tr>')
i:=0
Write(f,'<th>'+Str(i2)+'</th>')
Repeat
Write(f,'<td title="TestBit('+Str(i)+','+Str(i2)+')" style="backgroundcolor:'+Choice(TestBit(i,i2),"lightgreen","")+'">'+Str(TestBit(i,i2))+'</td>')
Until i++>40
Write(f,'</tr>')
Until i2++>6
Write(f,'</table>')
closeHTML(f)
EndProc
- 141 -
Betriebssystemfunktion
- 142 -
Betriebssystemfunktion
Beispiel für die Verwendung von testbit um eine Reihe von Checkboxen auf ihren Wert hin zu prüfen.
In der Oberfläche werden einige Checkboxen angeboten:
Über JavaScript wird abgefragt, ob die Checkbox geticket ist und in der Variablen i der Wert kummuliert.
var i=0;
if (document.getElementById("sStatus1").checked==true)
if (document.getElementById("sStatus2").checked==true)
if (document.getElementById("sStatus3").checked==true)
if (document.getElementById("sStatus4").checked==true)
{
{
{
{
i=i+1
i=i+2
i=i+4
i=i+8
}
}
}
}
Wir möchten prüfen, ob im Datenfeld Status ein Wert eingetragen ist, der auf die Selektion passt.
Serverseitig erfolgt die Abfrage auf den Wert I wie folgt:
If Log(iBit)/Log(2)+1=GetRField(U,"Status") or (!iBit in [1,2,4,8],TestBit(iBit,GetRField(U,"Status")-1))
Log(iBit)/Log(2) gibt den Exponenten zur Basis 2 zurück, der Wert muss um 1 erhöht werden, da unsere Auswahlliste mit dem Startwert=1 beginnt.
Die Abfrage prüft, ob genau eine Checkbox angeklickt ist und falls nein, dann erfolgt die Prüfung über TestBit.
Bei der reinen Selektion über testbit würden alle kleineren Werte ebenfalls selektiert werden, obwohl in der Oberfläche nur eine Checkbox geticket ist.
- 143 -
Betriebssystemfunktion
Für n gilt:
0: niederwertigstes Bit
31 (bzw. 63):höchstwertige Bit
TESTBIT(255,0) = 1
TESTBIT(256,0) = 0
17.2
17.2.1
Funktionen in easy geschrieben
Potenztieren
Procedure pot(n:Real;p:Integer):Real
Return Exp(p*Log( n ))
EndProc
17.2.2
Maximumwert
Procedure maximum(i1,i2:Real):Real
If i1<i2
Return i2
Else
Return i1
End
EndProc
17.2.3
Minimumwert
Procedure minimum(i1,i2:Real):Real
If i1<i2
Return i1
Else
Return i2
End
EndProc
- 144 -
Betriebssystemfunktion
Procedure dezimalgrad2gms(nDezimalgrad:Integer;Var grad,minuten:Integer;Var sekunden:Real)
Var dezimalgrad:String
Var minSek:Real
dezimalgrad:=Str(nDezimalgrad)
If Length(dezimalgrad)<7
dezimalgrad:='0'+dezimalgrad
End
grad:=Val(dezimalgrad[1,2])
minSek:=Val(RightStrStart(dezimalgrad,3))/100000*60
minuten:=Int(minSek)
sekunden:=Frac(minSek)*60
EndProc
Procedure number2BytesLittleEndian(Var bytes:Byte[];number,offset,noBytes:Integer)
Var i,p:Integer
i:=noBytes-1
While i>=0
p:=pot(256,i)
bytes[offset+i]:=Int(number/p)
number:=number-bytes[offset+i]*p
i-End
EndProc
Procedure number2BytesBigEndian(Var bytes:Byte[];number,offset,noBytes:Integer)
Var i,p:Integer
i:=0
While i<noBytes
p:=pot(256,noBytes-1-i)
bytes[offset+i]:=Int(number/p)
number:=number-bytes[offset+i]*p
i++
End
EndProc
Procedure bytes2numberLittleEndian(Var bytes:Byte[];offset,noBytes:Integer):Integer
Var i:Integer
Var number:Integer
i:=noBytes-1
- 145 -
Betriebssystemfunktion
While i>=0
number:=number+bytes[offset+i]*pot(256,i)
i-End
Return number
EndProc
Procedure bytes2numberBigEndian(Var bytes:Byte[];offset,noBytes:Integer):Integer
Var i:Integer
Var number:Integer
i:=0
While i<noBytes
number:=number+bytes[offset+i]*pot(256,noBytes-1-i)
i++
End
Return number
EndProc
Procedure valN(n:String):Byte
Var ascn:Byte
ascn:=Asc(Lower(n))
If ascn>Asc('9')
Return ascn-95+8
Else
Return Val(n)
End
EndProc
Procedure baseAnyTo10(n:String;r:Byte):Integer
Var i,sm,st:Integer
i:=Length(n)
While i>0
st:=valN(n[i,1])
sm:=sm+st*pot(r,Length(n)-i)
i-End
Return sm
EndProc
- 146 -
Betriebssystemfunktion
Procedure base10toAny(A:Integer;radix:Byte):String
Var s:String = '';
Var hex:String
Var i:Integer:=-1
hex:='0123456789ABCDEF'
While (A >= radix)
s:=s + hex[(A Mod radix)+1,1];
A:= Int(A / radix);
End
s:=s+hex[A+1,1]
Return reverse(s);
EndProc
Procedure baseconv(n:String;s,d:Byte):String
Return base10toAny(baseAnyTo10(n,s),d)
EndProc
- 147 -
Betriebssystemfunktion
17.2.4
Zufallszahl zwischen l und h
Procedure RandomRange(l,h:Integer):Integer
Return l+Random(h-l+1)
EndProc
Tangens
/*<doku>
<author>Sven Killig</author>
<created>20.03.2005</created>
Quelle: Bronnstein
Tangens
</doku>*/
Procedure Tan(a:Real):Real
Return Sin(a)/Cos(a)
EndProc
/*<doku>
<author>Sven Killig</author>
<created>20.03.2005</created>
Quelle: http://www.kh-gps.de/gk2koord.bas
</doku>*/
Procedure gaussKrueger2dezGrad(rw,hw:Real;Var gl,gb:Real)
Var rho : Real = 180 / PI
Var e2 : Real = 0.0067192188
Var c : Real = 6398786.849
Var mKen : Integer = Int(rw / 1000000)
Var rm : Real = rw - mKen * 1000000 - 500000
Var bI : Real = hw / 10000855.7646
Var bII : Real = bI * bI
Var bf : Real = 325632.08677 * bI * ((((((0.00000562025 * bII + 0.00022976983) * bII - 0.00113566119) * bII + 0.00424914906) *
bII - 0.00831729565) * bII + 1))
bf := bf / 3600 / rho
Var co : Real = Cos(bf)
Var g2 : Real = e2 * (co * co)
- 148 -
Betriebssystemfunktion
Var g1 : Real = c / SqRt(1 + g2)
Var t : Real = Tan(bf)
Var fa : Real = rm / g1
gb := bf - fa * fa * t * (1 + g2) / 2 + fa * fa * fa * fa * t * (5 + 3 * t * t + 6 * g2 - 6 * g2 * t * t) / 24
gb := gb * rho
Var dl : Real = fa - fa * fa * fa * (1 + 2 * t * t + g2) / 6 + fa * fa * fa * fa * fa * (1 + 28 * t * t + 24 * t * t * t * t) / 120
gl := dl * rho / co + mKen * 3
EndProc
Procedure dezGrad2gaussKrueger(laDezimal,brDezimal:Real;sy:Integer;Var x,y:Real)
/*<doku>
<author>Sven Killig</author>
<created>07.09.2005</created>
Quelle: http://www.dsdt.info/tipps/?id=627
<parameter>sy: Zielsystemnummer (Meridiankennziffer)</parameter>
</doku>*/
Var rho : Real = 180 / PI
Var e2 : Real = 0.0067192188
Var c : Real = 6398786.849
Var bf : Real = brDezimal / rho
Var g : Real = 111120.61962 * brDezimal - 15988.63853 * Sin(2*bf) + 16.72995 * Sin(4*bf) - 0.02178 * Sin(6*bf) + 0.00003 * Sin(8*bf)
Var co : Real = Cos(bf)
Var g2 : Real = e2 * (co * co)
Var g1 : Real = c / SqRt(1 + g2)
Var t : Real = Tan(bf)
Var dl : Real = laDezimal - sy * 3
Var fa : Real = co * dl / rho
y := g + fa * fa * t * g1 / 2 + fa * fa * fa * fa * t * g1 * (5 - t * t + 9 * g2) / 24
Var rm : Real = fa * g1 + fa * fa * fa * g1 * (1 - t * t + g2) / 6 + fa * fa * fa * fa * fa * g1 * (5 - 18 * t * t * t * t * t * t) / 120
x := rm + sy * 1000000 + 500000
EndProc
Procedure dezGrad2gaussKruegerDatum(laDezimal,brDezimal:Real;sy:Integer;Var x,y:Real)
/*<doku>
<author>Sven Killig</author>
<created>08.09.2005</created>
</doku>*/
Var as:String[]
- 149 -
Betriebssystemfunktion
//
split(as,CGIExecString('echo','"'+Str(laDezimal,0,15,'','','.')+' '+Str(brDezimal,0,15,'','','.')+'" | cs2cs +proj=latlong +datum=WGS84 +to
+init=epsg:'+Str(31464+sy)+' +towgs84=606,23,413'),Chr_TAB)
x:=Val(as[0])
y:=Val(as[1])
EndProc
Procedure gaussKrueger2dezGradDatum(x,y:Real;Var laDezimal,brDezimal:Real)
/*<doku>
<author>Sven Killig</author>
<created>14.09.2005</created>
</doku>*/
Var as:String[]
//
split(as,CGIExecString('echo','"'+Str(x,0,15,'','','.')+' '+Str(y,0,15,'','','.')+'" | cs2cs +proj=latlong +datum=WGS84 +to +init=epsg:'+Str(31464+4)+'
+towgs84=606,23,413 -I -f "%.15f"'),Chr_TAB)
laDezimal:=Val(as[0])
brDezimal:=Val(as[1])
EndProc
procedure HexToNum(c:STRING):REAL
/*<doku>
<author>Horst Klier</author>
<created>20.07.2006</created>
<about>Alte Routine von plaudern.de, wird von HTMLColFade benutzt</about>
</doku>*/
VARDEF n,nSt : REAL
VARDEF i,nRet : REAL
nRet:=0
nSt:=0
i:=Length(c)
while i>=1
if Upper(c[i,1])="F"
n:=15
elsif Upper(c[i,1])="E"
n:=14
elsif Upper(c[i,1])="D"
n:=13
elsif Upper(c[i,1])="C"
n:=12
elsif Upper(c[i,1])="B"
- 150 -
Betriebssystemfunktion
n:=11
elsif Upper(c[i,1])="A"
n:=10
else
n:=VAL(c[i,1])
end
nRet:=n*(Exp(nSt*Log(16)))+nRet
nSt:=nSt+1
i:=i - 1
end
Return nRet
endproc
procedure NumToHex(n:REAL):STRING
/*<doku>
<author>Horst Klier</author>
<created>20.07.2006</created>
<about>Alte Routine von plaudern.de, wird von HTMLColFade benutzt</about>
</doku>*/
VARDEF c : STRING
VARDEF i : REAL
c:=""
while n>0
i:=n mod 16
if i=15
c:='F'+c
elsif i=14
c:='E'+c
elsif i=13
c:='D'+c
elsif i=12
c:='C'+c
elsif i=11
c:='B'+c
elsif i=10
c:='A'+c
else
c:=STR(i)+c
end
- 151 -
Betriebssystemfunktion
n:=n div 16
end
if c=""
c:="00"
end
if Length(c)<2
c:="0"+c
end
Return c
endproc
procedure Fade(n1,n2,nMax,nAct:Integer):Real
/*<doku>
<author>Horst Klier</author>
<created>20.07.2006</created>
<about>Alte Routine von plaudern.de, wird von HTMLColFade benutzt</about>
</doku>*/
if nMax<=0 then nMax:=1 end
Return n1 - ((n1 - n2)*(nAct/nMax))
endproc
procedure HTMLColFade(cCol1,cCol2:STRING;nMax,nAct:REAL):STRING
/*<doku>
<author>Horst Klier</author>
<created>20.07.2006</created>
<about>Alte Routine von plaudern.de</about>
</doku>*/
.. Return cCol1
VARDEF cCol : STRING
VARDEF n : REAL[5]
n[0]:=HexToNum(cCol1[1,2])
n[1]:=HexToNum(cCol1[3,2])
n[2]:=HexToNum(cCol1[5,2])
n[3]:=HexToNum(cCol2[1,2])
n[4]:=HexToNum(cCol2[3,2])
n[5]:=HexToNum(cCol2[5,2])
cCol:=NumToHex(Fade(n[0],n[3],nMax,nAct))
cCol:=cCol+NumToHex(Fade(n[1],n[4],nMax,nAct))
cCol:=cCol+NumToHex(Fade(n[2],n[5],nMax,nAct))
- 152 -
Betriebssystemfunktion
Return cCol
endproc
Procedure HTMLReadableColor(cCol:String):String
/*<doku>
<author>Horst Klier</author>
<created>20.07.2006</created>
<about>Wenn die übergebene Farbe Hell ist wird 000000 zurück geliefert,
wenn die übergebene Farbe Dunkel ist, wird ffffff zurück geliefert.</about>
</doku>*/
VARDEF n : Integer[2]
Var cRet : String
n[0]:=HexToNum(cCol[1,2])
n[1]:=HexToNum(cCol[3,2])
n[2]:=HexToNum(cCol[5,2])
If calc(n[0]+n[1]+n[2])/3>128 then
cRet:='000000'
Else
cRet:='ffffff'
End
Return cRet
EndProc
Procedure B_XOr(a,b:Integer):Integer
/*<doku>
<author>Sven Killig</author>
<created>05.04.2007</created>
</doku>*/
Return (a B_Or b) B_And ((B_Not a) B_Or (B_Not b))
EndProc
Procedure LogN(x, base:Real):Real
/*<doku>
<author>Sven Killig</author>
<created>05.05.2008</created>
<return>log<sub>n</sub>(x)</return>
</doku>*/
- 153 -
Betriebssystemfunktion
Return Log(x)/Log(base)
EndProc
Procedure TestValue(value, test:Integer):Integer
/*<doku>
<author>Sven Killig</author>
<created>05.05.2008</created>
</doku>*/
Return TestBit(value,LogN(test,2))
EndProc
Procedure lib_arithmeticMean(Var v:Real[]):Real
/*<doku>
<author>Sven Killig</author>
<created>13.10.2008</created>
</doku>*/
Var i,N:Integer
Var sm:Real
N:=High(1,v)+1
i:=1
While i<=N
sm:=sm+v[i-1]
i++
End
Return sm/N
EndProc
Procedure lib_StandardDeviation(Var v:Real[]):Real
/*<doku>
<author>Sven Killig</author>
<created>13.10.2008</created>
</doku>*/
Var i,N:Integer
Var sm,rMean:Real
N:=High(1,v)+1
//WriteLog('math.lib_StandardDeviation.N='+Str(N))
- 154 -
Betriebssystemfunktion
i:=1
While i<=N
//teilweise TDBReal-Überlauf:
//sm:=sm+pot(v[i-1],2)
sm:=sm+v[i-1]*v[i-1]
i++
End
//teilweise TDBReal-Überlauf?:
//Return SqRt(sm/N-pot(lib_arithmeticMean(v),2))
rMean:=lib_arithmeticMean(v)
Return SqRt(sm/N-rMean*rMean)
EndProc
Procedure deg2rad(rDeg:Real):Real
/*<doku>
<author>Sven Killig</author>
<created>30.03.2009</created>
<about>Convert degrees to radiant</about>
</doku>*/
Return rDeg*PI/180
EndProc
- 155 -
Betriebssystemfunktion
18.
Laufzeitschalter der tdbengine
Über Laufzeitschalter wird das Verhalten der tdbengine gesteuert. Mit Ausnahme von AK werden alle Schalter erst zur Laufzeit aktiviert und gelten grundsätzlich
global.
18.1
Alle Schalter in der Übersicht
Schalter
Ak
Bedeutung
Abkürzung
Wirkung
Abkürzungen bei Feldbezeichnern sind erlaubt (1)
Vorgabe
0
ec
Error Check
Fehlerbehandlung durch Programm (1)
0
em
Exact Match
Volltextindierung und -suche vergleichen Masken exakt (1)
0
de
Descending
Subreport kehrt Reihenfolge der Datensätze um (1)
0
nb
No Break
Das Programm kann nicht durch eine Datei namens 'abort' abgebrochen werden (1)
0
nv
Numeric Values
VAL erlaubt nur numerische Konstanten (1)
1
- 156 -
Betriebssystemfunktion
18.2
Setzen und Auslesen
Zum Setzen eines Schalters bestehen zwei Möglichkeiten:
Punktbefehl (veraltet)
Funktion SetPara
Ein Punktbefehl ist eine Programmzeile, die mit einem Punkt beginnt (wobei führende Leerzeichen ignoriert werden). Nach diesem Punkt werden die einzelnen
Schalter mit dem jeweiligen Wert hintereinander - und jeweils durch Komma getrennt - geschrieben:
.ec 1, nb 0
Da in der nächsten Hauptversion (6.3.x) die Zeilenstruktur in EASY-Programmen aufgehoben wird, sollte von dieser Art der Schaltersetzung nicht mehr
Gebrauch gemacht werden.
An dessen Stelle tritt die Funktion SetPara
18.3
SetPara(Schalter:STRING):REAL
Hier werden die Schalter als String übergeben (ohne führenden Punkt) –ebenfalls durch Komma getrennt:
SetPara('ec1,nb0')
Der Rückgabewert der Funktion ist immer 0, bei einem illegalen Befehl wird ein Laufzeitfehler ausgelöst.
Der Wert eines Schalters kann mit der Funktion GetPara abgefragt werden.
GetPara(Schalter:STRING):REAL
GetPara('ec')->0|1(je nachdem)
18.4
18.4.1
... und im Detail
ak
- 157 -
Betriebssystemfunktion
Dieser Schalter wird nur bei dynamischen Tabellenauswertungen verwendet, also beispielweise bei FindAndMark oder SUB wenn hier auf Datenfelder via
Feldbezeichner Bezug genommen wird. Es handelt sich um ein recht altes Relikt aus DOS-TDB-Zeiten. Im Normalfall sollte man es bei der Grundeinstellung
(Abkürzungen verboten) belassen.
18.4.2
ec
Bei allen kritischen Fehlerzuständen erzeugt die tdbengine einen Laufzeitfehler mit einem entsprechenden Eintrag in der Datei bin/error.log. Erfolgt der Fehler bei
einer Variablenzuweisung, so kann der Laufzeitfehler abgefangen werden, indem der Schalter ec 1 gesetzt wird. Die Fehlerbehandlung muss dann vom
Programm übernommen werden.
Die Behandlung der Schalter als Laufzeitfunktion (und nicht etwa als Flag zur Codeerzeugung) bedingt, dass bei einer lokalen Verwendung der Schalter explizit
zurückgesetzt werden muss.
PROCEDURE OpenTable(path : STRING) : REAL
VARDEF result : REAL
SetPara('ec 1') // Fehlerbehandlung übernehmen
result:=OpenDB(path)
RETURN result
SetPara('ec 0') // Fehlerbehandlung wieder(?) durch System
ENDPROC
Hier wird das Zurücksetzen des Schalters nie erreicht, und so bleibt im restlichen Programm der Schalter gesetzt, bis er irgendwo eventuell wieder zurückgesetzt
wird. Deshalb die bessere Lösung:
PROCEDURE OpenTable(path : STRING) : REAL
VAR result, t_ec : INTEGER
t_ec:=GetPara('ec') // äussere Stellung merken
SetPara('ec 1') // Fehlerbehandlung übernehmen
result:=OpenDB(path) // kein Laufzeitfehler
SetPara('ec '+str(t_ec)) // äussere Stellung wieder herstellen
RETURN result
ENDPROC
In diesem Beispiel ging es nur um die Positionierung des Schalters. Zur Fehlerbehandlung selbst stellt EASY die folgenden Funktionen zur Verfügung:
TDB_LastError liefert den Fehlercode
TDB_ErrorStr(TDB_LastError) liefert die Fehlermeldung als String
Zudem wird folgenden Systemvariable gesetzt (hier ist auf Groß/Kleinschreibung zu achten) :
- 158 -
Betriebssystemfunktion
TDB_ErrorMsg liefert den (rückübersetzten) Ausdruck, in dem der Fehler auftrat
Hier nun eine Beispiel mit Fehlerbehandlung:
PROCEDURE OpenTable(path : STRING; VAR system_msg : STRING) : REAL
VAR result, t_ec : INTEGER
t_ec:=GetPara('ec') // äussere Stellung merken
SetPara('ec 1')
// Fehlerbehandlung übernehmen
result:=OpenDB(path) // kein Laufzeitfehler
IFTDB_LastError=0 // alles okay
THEN system_msg:='done.'
ELSE system_msg:=TDB_ErrorStr(TDB_LastError) // Meldung nach aussen geben
END
SetPara('ec '+str(t_ec)) // äussere Stellung wieder herstellen
RETURN result
ENDPROC
18.4.3
em
Mit diesem Schalter wird die Behandlung der Maskenzahl bei der Volltextindierung und -suche gesteuert. Näheres siehe in der Dokumentation zur Volltextsuche.
18.4.4
de
Damit wird die Reihenfolge der Datensätze der Primärtabelle bei (Sub-)Reports festgelegt. Normalerweise ist die Reihenfolge aufsteigend. Mit SetPara('de
1') kann auf absteigende Reihenfolge umgeschaltet werden.
erst aufsteigend ...
VAR db : INTEGER
VARSel _: STRING
db:=OpenDB('database/adressen.dat')
Access(db,'name.ind')
Sel:='$Name FROM "A" TO "G"'
SUB _sel
CGIWriteLn(GetField(db,'Name'))
ENDSUB
...
- 159 -
Betriebssystemfunktion
Ausgabe:
Alzheimer
Bernholzer
Dorfmeister
Elstner
Friedrichs
... dann absteigend
VAR t_de : INTEGER
...
t_de:=GetPara('de') // Reihenfolge merken
SetPara('de 1') // Reihenfolge umdrehen
SUB _DBName(db)
CGIWriteLn(GetField(db,'Name'))
ENDSUB
SetPara('de '+str(t_de)) // Reihenfolge wieder herstellen
...
Ausgabe:
Friedrichs
Elstner
Dorfmeister
Bernholzer
Alzheimer
18.4.5
nb
Um bei einer Endlosschleife (oder einem sonstwie irrlaufenden Programm) eine einfache Abbruchmöglichkeit (auch für Nicht-Roots) zu haben, wird jede
Sekunde während des Programmlaufs das Vorhandensein einer Datei mit dem Namen 'abort' im Verzeichnis der tdbengine geprüft. Ist eine solche Datei
vorhanden, wird das Programm abgebrochen und (falls die Rechte dazu ausreichen) die Datei gelöscht.
Während der Testphase eines Programm sollte dieser Schalter auf 0 (Vorgabe) gesetzt bleiben. Wenn aber ein Programm ausgetestet und Endlosschleifen
ausgeschlossen sind, kann der Schalter auf 1 gesetzt werden, was zu einer deutlich spürbaren Performancesteigerung führt.
Hinweis: Der Abbruch mittels "abort" funktioniert nicht bei der Ausführung der Systemfunktionen, also beispielsweise während eines Indizierungslaufs.
- 160 -
Betriebssystemfunktion
Tipp: Falls sich die tdbengine "komisch" verhält und längere Programme ohne Fehlermeldung abbricht, so kann dafür eine "abort"-Datei verantwortlich sein, die
nicht gelöscht werden kann (weil die Rechte des anonymen http-users nicht ausreichen oder es sich um ein Verzeichnis handelt)
18.4.6
nv
Die Stellung dieses Schalters wird nur im Zusammenhang mit der Standard-Funktion Val() ausgewertet. Diese Funktion interpretiert das übergebene StringArgument als EASY-Ausdruck und berechnet dessen Wert.
In der eingeschränkten Version (Schalterstellung 1 = Vorgabe) werden nur gültige Zahlenkonstanten ausgewertet. Alle anderen Ausdrücke werden mit dem
Fehler "illegale Zahlenkonstante" zurückgewiesen.
In der erweiterten Version (Schalterstellung 0) sind hingegen beliebige Ausdrücke zugelassen. Dies kann zu einem massiven Sicherheitsproblem werden, wenn
ungefilterte CGI-Varia
blen an die Funktion Val() übergeben werden.
x:=Val(GetQueryString('recno'))
Erfolgt der Programmaufruf mit ...?recno=rm+-r+*, so erhalten Sie mit "nv 1" einen Programmabbruch mit Fehlermeldung, mit "nv 0" jedoch eine Katastrophe.
- 161 -
Betriebssystemfunktion
19.
Weitere Funkionen unsortiert
19.1
autofield() - Liefert die Feldnummer des Feldes, das die Autonummer speichert
Beschreibung
Bei der zentralen Datenerfassung und -bearbeitung ist es besser, auf Autonummern Bezug zu nehmen, als mit physikalischen Satznummern zu arbeiten.
Wichtig: Sind schreibende Zugriffe auf eine Tabelle erlaubt, sollten alle Programme, die mit dieser Tabelle arbeiten, einen gemeinsamen (binären)
Semaphoren einsetzen. Dadurch wird sichergestellt, daß immer nur ein Prozess auf die Tabelle zugreift.
Ist die Tabelle automatisch numeriert, so können nun mit AutoField() auch dann eindeutige Bezüge zu den Datensätzen hergestellt werden, wenn zwischen den
Zugriffen die Sperre wieder aufgehoben wird.
Syntax
AUTOFIELD(db : INTEGER) : INTEGER
Parameter
db : Tabellenhandle, wie ihn OPENDB liefert
Ergebnis
Nummer des Feldes, das automatisch nummeriert wird, 0, wenn kein solches vorhanden ist
19.2
copymemo() - Kopiert ein Memo in eine (externe) Textdatei
Beschreibung
Der in einem Memo enhaltene Text wird in eine externe Textdatei kopiert. Der Zeichsatz kann über den to_ascii-Schalter ausgewählt werden.
Folgende Ziele sind erlaubt:
externe Datei
Ramtext ("ramtext:...")
Standardausgabe ("con")
Syntax
COPYMEMO(memo : fielddesc; pfad : STRING [to_ascii : INTEGER]) : INTEGER
Parameter
memo : Tabellenhandle, Feldnummer (oder)
Tabellenhandle, Feldbezeichner
pfad : Pfad zur externen Datei
- 162 -
Betriebssystemfunktion
to_ascii : 1 -> die externe Datei wird im ASCII-Zeichensatz angelegt, sonst ANSI-Zeichensatz
Ergebnis
Fehlercode
0 : erfolgreich kopiert
19.3
delrec() - Löscht einen Datensatz physikalisch aus einer Tabelle
Beschreibung
Die tdbengine löscht einen Datensatz aus einer Tabelle, indem es sie den bislang letzten Datensatz an die Stelle des zu löschenden Satzes kopiert und die
Tabellengröße um einen Eintrag veringert (beim letzten Datensatz mit nur die Größe veringert).
Der Rückgabewert der Funktion hängt vom aktuellen Zugriff ab:
Zugriff über physikalische Satznummer -> recno (bzw. recno-1, wenn recno=letzter Satz in Tabelle)
Zugriff über Markierungsliste -> Nummer des nächsten Satzes in der Markierungsliste (bzw. des vorherigen, wenn recno der letzte Eintrag in der Markierungslist
war, bzw. 0, wenn recno nicht in Markierungsliste war)
Zugriff über Index: Nummer des nächsten Datensatzes bzgl. der im Index definierten Ordnung (bzw. des vorherigen, wen es keinen nächsten mehr gibt)
Syntax
DELREC(db : INTEGER; recno : INTEGER) : INTEGER
Parameter
db : Tabellenhandle von OPENDB
recno : physikalische Satznummer des zu löschenden Datensatzes
Ergebnis
Physikalische Satznummer des Datensatzes, der an Stelle des gelöschten Datensatzes gerückt ist.
19.4
editoff() - Satzsperre freigeben
Beschreibung
Eine mit EditOn veranlaßte Satzsperre wieder aufheben.
Syntax
EDITOFF(db : INTEGER)
- 163 -
Betriebssystemfunktion
Parameter
db : Tabellenhandle
19.5
editon() - Satzsperre einrichten
Beschreibung
Versucht eine Satzsperre auf den aktuellen Datensatz der Tabelle einzurichten. Falls die Satzsperre eingerichtet werden konnte, muss anschließend die Funktion
EditOff aufgerufen werden.
Die Funktion scheitert dann, wenn ein anderes prg den Satz schon gesperrt hat.
Syntax
EDITON(db : INTEGER) : INTEGER
Parameter
db : Tabellenhandle
Ergebnis
1 : Sperre ist eingerichtet
0 : Sperre ist nicht eingerichtet
- 164 -
Betriebssystemfunktion
Beispiel 1: EditOn
PROCEDURE Mutation
VAR BUCHUNG : INTEGER
BUCHUNG:=OpenDB('buchung.dat')
REPEAT
IF EditOn(BUCHUNG)
SetRField(BUCHUNG,'gebucht',JA)
WriteRec(BUCHUNG,RecNo(BUCHUNG))
EditOff(BUCHUNG)
RETURN
ELSE
.. In Sperrungsschleifen immer warten, sonst eventuell Blockade!
Pause(10)
END
UNTIL 0
ENDPROC
- 165 -
Betriebssystemfunktion
- 166 -
Betriebssystemfunktion
19.6
exists() - Prüft die Erfüllbarkeit einer Bedingung
Beschreibung
Die Funktion erwartet als Parameter eine gültige Selektion mit direkten Tabellenzugriffen. Aus diesem Grund ist sie in der tdbengine nur dynamisch einsetzbar:
PrimTable(firmen)
Exp:='result:=exists($ansprechpartner.Name="Meier")'
DO _Exp
Dabei geht die Funktion davon aus, dass die Tabellen firmen und ansprechpartner relational verknüpft sind (andernfalls wird der aktuelle Datensatz aus firmen
mit der kompletten Tabelle ansprechpartner verknüpft). Das Ergebnis ist 1, wenn mit dem aktuellen Datensatz von firmen (wenigstens) ein Datensatz verknüpft
ist, der die Bedingung erfüllt.
Syntax
EXISTS(Selection) : INTEGER
Parameter
Selection : logischer Ausdruck
Ergebnis
1 :Selection ist erfüllbar
0 : Selection ist nicht erfüllbar
- 167 -
Betriebssystemfunktion
19.7
fields() - Feld-Definition für Volltextindizierung
Beschreibung
FIELDS wird nur im Zusammenhang mit der Volltextindizierung eingesetzt. Die Funktion kommt ausschließlich als Parameter von SCANRECS, SCANREC und
UNSCANREC zum Einsatz.
Der Parameter von FIELDS gibt die Felder an, über die der Volltextindex gebildet wird.
Syntax
FIELDS(Feld-Definition : STRING):STRING
Parameter
Feld-Definition:
"complete" |
"complete:n" (n Zahlenkonstante) |
"Felddef{,Felddef}" mit
Felddef=Datenfeld[:n] (n Zahlenkonstante)
Felddef=$Feldnummer | [$]Feldbezeichner
Ergebnis
undefiniert
- 168 -
Betriebssystemfunktion
19.8
fileno() - Liefert Handle der aktuellen Primärtabelle
Beschreibung
Wenn mehrere (relational verknüpfte) Tabellen geöffnet sind, kann immer nur eine die Funktion der Primärtabelle erfüllen. Im Normalfall ist dies die zeitlich
zuletzt geöffnete Tabelle. Mit der Funktion PRIMTABLE kann jede geöffnete Tabelle zur Primärtabelle gemacht werden.
Syntax
FILENO : INTEGER
Ergebnis
Handle auf die aktuelle Primärtabelle
- 169 -
Betriebssystemfunktion
19.9
findauto() - Liefert die physikalische Satznummer zu einer AUTO-Nummer
Beschreibung: Es gilt:
IF AutoField(DB)>0 THEN
RETURN FindRec(DB,Str(a_no),IndName(DB,0),1)
ELSE
RETURN a_no
END
Syntax
FINDAUTO(DB, a_no : INTEGER) : INTEGER
Parameter
DB : Tabellenhandle von OPENDB
a_no : Auto-Nummer Ergebnis Die physikalische Satzummer desjenigen Datensatzes, der in seinen AUTO-INCREMENT-Feld den Wert a_no
enthält.
Wenn DB nicht automatisch nummeriert wird, liefert FINDAUTO den Paramater a_no zurück.
- 170 -
Betriebssystemfunktion
19.10 findrec() - Sucht einen Datensatz über einen Index
Beschreibung
Der SearchStr muss adäquat zur Index-Beschreibung aufgebaut sein. Enthält eine Komponente ein Komma, so muss diese geklammert werden.
Beispiele:
Index-Beschreibung ="Name,Vorname,Ort"
SearchStr = "Schwalm,Till,München", "Lichtenberg,Franz,(München,Moosach)"
Werden die Informationen vom Anwender geholt, ist eine Klammerung für alle Komponenten sinnvoll:
SearchStr:='('+s_Name+'),('+s_Vorname+'),('+s_Ort+')'
Wird kein Index angegeben, so wird im aktuellen (mittels access eingestellten) Index gesucht. Steht der Zugriff nicht auf einem Index (sondern auf Nummer oder
Markierung), so erfolgt die Fehlermeldung "Illegaler Zugriff").
Mit dem Modus 0 wird der (bzgl. der Index-Ordnung) kleinste Eintrag gesucht, der gleich oder größer dem gesuchten ist. Ist hier kein Eintrag vorhanden, so wird
der größte Eintrag gesucht, der gleich oder kleiner dem gesuchten ist. In diesem Modus wird also nur dann 0 zurückgeliefert, wenn die Tabelle komplett leer ist.
Im Modus 1 wird hingegen nur dann ein Wert ungleich 0 geliefert, wenn ein Eintrag gefunden wird, der in allen Komponenten mit der Suche übereinstimmt.
Syntax
FindRec(db : INTEGER; SearchStr : STRING [;Index[;Mode : INTEGER]]) : INTEGER
Parameter
db : Tabellenhandle von OpenDB
SearchStr : gesuchte Information
index : Indexname oder Indexnummer(>=0)
Mode : 0 (Vorgabe) -> Suche nach Eintrag >= SearchStr; 1 -> Suche nach Eintrag = SearchStr
Ergebnis
0 -> kein Datensatz gefunden, ansonsten recno(db)
- 171 -
Betriebssystemfunktion
19.11 firstrec() - Liefert die Nummer des ersten Datensatzes bzgl. aktuellem Zugriff
Beschreibung
Je nachdem, wie der Zugriff mit ACCESS für die Tabelle gesetzt wurde, liefert FIRSTREC unterschiedliche Ergebnisse:
Markierung (-2): erster Datensatz in Markierungliste
Nummer (-1) : 1 (wenn Tabelle nicht leer)
Index (0..15) : Datensatz mit der kleinsten Indexinformation
Syntax
FIRSTREC(db : INTEGER) : INTEGER
Parameter
db : Tabellenhandle von OpenDB
Ergebnis
physikalische Satznummer des ersten Datensatzes
- 172 -
Betriebssystemfunktion
Beispiel: Vollständiges geordnetes Durchlaufen eines Tabelle über einen Index
db:=OpenDB(...);
access(db,1); // Zugriff auf Index setzen
x:=firstrec(db);
WHILE x DO
readrec(db,x);
... hier steht der Satz zur Verfügung
x:=nextrec(db)
END;
- 173 -
Betriebssystemfunktion
19.12 fsum() - Addiert die Feldinhalte aufeinanderfolgender Datenfelder
Beschreibung
Wenn in einer Tabellenstruktur mehrere nummerische Felder direkt aufeinander folgen, kann mit FSUM eine Summe über diese Felder gebildet werden.
Syntax
FSUM(DB : INTEGER; Feld; n : INTEGER) : INTEGER
Parameter
DB : Tabellenhandle von OPENDB
Feld : Feldnummer oder Feldbezeichner (als String)
n : Anzahl der zu summierenden Felder
Ergebnis
Summe über die angegeben Felder. Siehe auch
Beispiel:
[STRUCTURE]
...
field_20=Umsatz_01:NUMBER,8,2
field_21=Umsatz_02:NUMBER,8,2
field_22=Umsatz_03:NUMBER,8,2
field_23=Umsatz_04:NUMBER,8,2
field_24=Umsatz_05:NUMBER,8,2
field_25=Umsatz_06:NUMBER,8,2
field_26=Umsatz_07:NUMBER,8,2
field_27=Umsatz_08:NUMBER,8,2
field_28=Umsatz_09:NUMBER,8,2
field_29=Umsatz_10:NUMBER,8,2
field_30=Umsatz_11:NUMBER,8,2
field_31=Umsatz_12:NUMBER,8,2
..
FSUM(DB,'Umsatz_01',month(today)) liefert z.B. im Mai
Umsatz_01+Umsatz_02+Umsatz_03+Umsatz_04+Umsatz_05
- 174 -
Betriebssystemfunktion
19.13 getdef() - Schreibt die Struktur einer Tabelle in eine Textdatei
Beschreibung
Die Struktur einer der Tabelle wird so abgelegt, dass MAKEDB daraus wieder eine Tabelle generieren kann.
Syntax
GETDEF(db : INTEGER; textfile : STRING) : INTEGER
Parameter
db : Tabellenhandle von OPENDB
textfile : Pfad zu einer externen Textdatei oder einem Ramtext
Ergebnis
Fehlercode
0 : erfolgreich
19.14 getfield() - liefert den Inhalt eines Feldes aus dem Satzpuffer
Beschreibung
GETFIELD kann mit allen Feldtypen verwendet werden, ausser mit Memos und Blobs.
Wenn numerische Werte im Programm weiterverarbeitet werden sollen, ist GETRFIELD vorzuziehen.
Syntax
GETFIELD(db : INTEGER; field : INTEGER|STRING[; VAR buffer : BYTE[]]) : STRING
Parameter
db : Tabellenhandle von OpenDB
field : Feldbezeichner oder Feldnummer
buffer : Puffer
Ergebnis
Feldindhalt als STRING
- 175 -
Betriebssystemfunktion
19.14.1 Zugriff auf den Inhalt eines Arrays
Der Zugriff auf den Inhalt des Arrays erfolgt über die Funktion getfield.
Getfield hat einen 3 Parameter, den Satzbuffer.
Der Zugriff auf den aktuellen Datensatz erfolgt demnach über
Getfield(A,“Name“)
Der Zugriff auf den Inhalt des Satzbuffers über
Getfield(A,“Name“,buffer)
Hinweis:
Der Zugriff über die Funktion getrfield() auf den Satzbuffer ist nicht zulässig.
Hier wird auch ein Laufzeitfehler generiert.
19.15 getmarks() - speichert eine Markierungsliste
Beschreibung
In manchen Fällen kommt es vor, dass man die Markierungsliste zwischenpeichern muss, weil z.B. kurzfristig eine weitere Selektion auf die Tabelle
vorgenommen werden muss. Zu diesem Zweck bietet EASY die Funktionen »getmarks« und »putmarks«. Diese Funktionen können mit unterschiedlichen
Argumenten aufgerufen werden:
VAR marks : MARKS
getmarks(db,marks) // sichert die Markierungsliste von db in marks
putmarks(db,marks) // speichert die gesicherte Markierungsliste zurück
Hinweis: putmarks überschreibt die aktuelle Markierungsliste von db. Der Datentyp MARKS kann nur mit diesen beiden Funktionen verwendet werden. Der große
Vorteil, eine Variable vom Typ MARKS einzusetzen, besteht darin, dass
nur der wirklich benötigte Speicherplatz verwendet wird *
die Sortierung innerhalb der Markierungslist erhalten bleibt
Die Markierungsliste kann aber auch auf in einem INTEGER-Array gespeichert werden.
VAR marks : INTEGER[]
...
initarray(marks[nmarks(db)])
getmarks(db,marks)
putmarks(db,marks)
- 176 -
Betriebssystemfunktion
Nach »getmarks(db,marks)« liegen in »marks« die physikalischen Satznummern der Markierungsliste vor. Hier eine Prozedur, in der die Markierungsiste in einer
externen Textdatei gespeichert wird:
PROCEDURE SaveMarks(db : INTEGER);
VAR text,i : INTEGER;
VAR marks : INTEGER[];
initarray(marks[nmarks(db)]);
getmarks(db,marks);
IF text:=rewrite('marks_save') THEN
nloop(i,high(1,marks)-1,writeln(text,marks[i]));
close(text)
END
ENDPROC
Schließlich gibt es noch die Möglichkeit, eine Variable vom Type TBITS zu verwenden. Um richtig zu arbeiten, muss dieses Feld so dimensioniert werden, dass
es die gesamte Tabelle abbilden kann:
VAR marks : TBITS[]
...
initarray(marks[filesize(db)])
getmarks(db,marks)
putmarks(db,marks)
Hier gilt nach »getmarks(db,marks)«, dass * marks[x]=1 wenn der Datensatz mit der Satznummer x in der Markierungsiste ist * marks[x]=0 andernfalls »marks«
ist somit ein Abbild der Teilmenge der Tabelle, die durch die Markierungsliste festgelegt ist. Und wie bei jeder Menge, geht hier die Ordnung verloren! Der Vorteil
der Verwendung von TBITS liegt vor allem darin, dass mit den Funktionen * bitand(b_feld1,b_feld2) * bitor(b_feld1,b_feld2) * bitandnot(b_feld1,b_feld2) die
wichtigsten Mengenoperationen sehr effizient durchgeführt werden können. Alle drei Funktionen liefern die Cardinalität (Anzahl der Einsen) von »b_feld_1« nach
der jeweiligen Operation. bitand: b_feld1 := b_feld1 geschnitten mit b_feld2 bitor: b_feld1 := b_feld1 vereinigt mit b_feld2 bitandnot: b_feld1 := b_feld1
geschnitten mit dem Kompliment von b_feld2 (Mengendifferenz) Die Arbeit mit dem Datentyp TBITS ist so effizient, dass beispielsweise die Volltextsuche
grundsätzlich darauf zurückgreift (»markbits«). Man wird sie immer dann einsetzen, wenn mehrere - logisch verknüpfte - Selektionen auf eine Tabelle ausgeführt
werden.
Syntax
GETMARKS(db : INTEGER; VAR marks) : INTEGER
Parameter
db : Tabellenhandle von OpenDB
marks : MARKS | INTEGER[] | REAL[] | TBITS[]
- 177 -
Betriebssystemfunktion
Ergebnis
0 : Operation erfolgreich
sonst : Fehlercode (44 falscher Typ)
- 178 -
Betriebssystemfunktion
19.16
getrec() - Schreibt den Satzpuffer in ein Array
Syntax
GetRec(db : INTEGER; VAR buffer : BYTE[])
Parameter
db : Tabellenhandle von OpenDB
buffer : Puffer
Beispiel:
VAR buffer :BYTE[]
...
GetRec(db,buffer)
Beispiel 2
Satzbuffer in ein Array schreiben
Die Funktionen benötigt man dann, wenn man zum Beispiel den Inhalt eines Datensatzes vor dem Speichern und nach dem Speichern vergleichen möchte.
Ein Beispiel hierzu aus der Praxis.
Gegeben ist eine Adressdatenbank. Die spezielle Anforderung besteht darin, dass bei Änderung der Anschrift automatisch eine E-Mail-Benachrichtigung
generiert werden soll.
Hierzu ist es erforderlich, den Zustand vor der Änderung mit dem Zustand nach der Änderung vergleichen zu können.
Hier kommt die Funktion getrec ins Spiel.
Um mit getrec einen Satzpuffer speichern zu können, muss man zuerst ein Array vom Typ Byte[] definieren
Var buffer
: Byte[]
Das Array wird nicht dimensioniert, es genügt wenn man bei der Deklaration byte[] schreibt.
Das nachfolgende Programm Beispiel zeigt, wie ein Datensatz gelesen wird und anschließend der Satzinhalt in dem Array buffer mit der Funktion getrec
abgelegt wird.
Anschließend wird der Satzbuffer mit Formulardaten überschrieben.
Var buffer : Byte[]
VAR A : Integer = opendb(„adressen.dat”)
- 179 -
Betriebssystemfunktion
Readrec(A, findrec(A,”tdb”,”$Name”,1,1))
..Satzbuffer wird in buffer abgelegt
Getrec(A,buffer)
..Datensatz wird verändert
Setfield(A,”Name”, GetQueryString(“Name”))
Setfield(A,”Vorname”, GetQueryString(“Vorname”))
Writerec(A,recnr(A))
If getfield(A,”Name”)#getfield(A,”Name”,buffer)
.. eMail versenden
end
19.16.1
Sonderfall neuer Datensatz
ACHTUNG: GETREC IST NICHT GEEIGNET, UM DAMIT EINEN D ATENSATZ ZU DUPPLIZIEREN.
GETREC SETZT AUCH DIE AUTONR , SO DASS DOPPELTE AUTO-NUMMERN ENTSTEHEN UND DIES IST FATAL .
D AHER ZUM DUPPLIZIEREN IMMER MIT SETRECORD ARBEITEN
Bei einem neuen Datensatz kann man nicht mit getrec arbeiten.
Hier entsteht ein Laufzeitfehler, wobei die Fehlermeldung nicht besonders aufschlußreich ist.
Die Fehlermeldung sieht z.B: so aus:
12.10.2011 17:01:38.94: 58 (Keine Tabelle: GETREC (A,buffer)) in /var/www/develop/tdb7/program/getdata.prg(12904)
Muss man mit getrec arbeiten, so kann man sich dabei behelfen, dass man erst einmal speichert, damit ein physikalische Datensatz vorhanden ist.
Anschließend kann man den Inhalt des leeren Datensatzes in ein Array ablegen, ohne dass dies zu einem Laufzeitfehler führt.
Anbei ein Programm Beispiel, es wird ein leerer Datensatz angelegt und anschließend mit getrec der leerer Eintrag in ein Array abgelegt.
Der Grund für diese Programmierung ist der anschließende Aufruf einer Prozedur, der als Übergabeparameter das Array erwartet.
Da man das Byte Array nicht mit Initarray initialisieren kann, ist das der einzige Weg, damit das Array in der nachfolgenden Prozedur verwendet werden kann.
ReadRec(A,0)
SetRField(A,"CreatedOn",Unix_Now)
SetRField(A,"ChangedOn",Unix_Now)
SetField(A,"ChangedBy",sUser)
SetField(A,"ChangedBy",sUser)
WriteRec(A,FileSize(A)+1)
ReadRec(A,RecNr(A))
- 180 -
Betriebssystemfunktion
GetRec(A,buffer)
SetRecord(AQ,A)
WriteRec(A,RecNr(A))
addEintragInCH_Address(A,buffer)
Für diejenigen, die sehen möchten, wie man in eine Protokolldatei alle Feldänderungen dokumentieren kann, ist die nachfolgende Prozedur interessant.
Hier folgt nämlich der Abgleich zwischen dem vorhergehenden Feldinhalte und dem aktuellen Feldinhalt.
Wird eine Abweichung festgestellt, so wird in die Tabelle ch_address.dat ein Eintrag vorgenommen.
Procedure addEintragInCH_Address(A:Integer;Var buffer:Byte[])
/* 12.10.2011 16:28:05/hg
Protokoll der Feldänderungen in address.dat
*/
Var l : Integer
Var CHA : Integer = OpenDB(sPath+"database/ch_address.dat","",0,15)
l := LabelNr(A,"Name")
Repeat
If GetField(A,l)#GetField(A,l,buffer) then
ReadRec(CHA,0)
SetRField(CHA,"AutoID_Adresse",GetRField(A,"AutoID"))
SetField(CHA,"Feld",Label(A,l))
SetField(CHA,"vorher",GetField(A,l,buffer))
SetField(CHA,"nachher",GetField(A,l))
SetField(CHA,"ChangedOn_Vorher",GetField(A,"ChangedOn",buffer))
SetRField(CHA,"ChangedOn_Nachher",Unix_Now)
SetField(CHA,"ChangedBy",sUser)
WriteRec(CHA,FileSize(CHA)+1)
End
Until l++>labelNr(A,"Email")
CloseDB(CHA)
EndProc
Die Protokolleinträge sehen dann so aus, wie in der nachfolgenden Abbildungen dargestellt:
- 181 -
Betriebssystemfunktion
Darstellung einer Tabelle in baseman.prg
19.17 getrfield() - liefert den Inhalt eines Feldes aus dem Satzpuffer
Beschreibung
GETRFIELD kann mit allen numerischen Feldtypen verwendet werden:
NUMBER 1,2,4,6,8
AUTO
LINK
DATE
TIME
UTIME
SELECT
GETRFIELD liefert immer die volle im Feld gespeicherte Genauigkeit
Syntax
GETRFIELD(db : INTEGER; field : INTEGER|STRING) : INTEGER
Parameter
db : Tabellenhandle von OpenDB
- 182 -
Betriebssystemfunktion
field : Feldbezeichner oder Feldnummer
Ergebnis
Feldindhalt als Zahl
19.18 incrfield() - Incrementiert den Wert eines Datenfeldes
Beschreibung:
Diese Funktion arbeitet mit den Feldwerten des Satzpuffers. Der Offset darf auch negativ sein.
Die Funktion ist aus Performacegründen der Konstruktion
SETRFIELD(db,field,GETRFIELD(db,field)+offset)
vorzuziehen.
Syntax
INCRFIELD(db : INTEGER; field : INTEGER|STRING [; offset : INTEGER]) : INTEGER
Parameter
db : Tabellenhandle von OpenDB
field : Feldnummer oder Feldbezeichner
offset: (Vorgabe 1) Wert, der zum aktuellen Feldwert addiert wird
Ergebnis
Feldwert nach Ausführung der Funktion
19.19 Feldbezeichner ermitteln – label()
Beschreibung:
Wie allen Funktionen, die auf eine Tabellen-/Feldkombination zugreifen, wird bei illegalen Parametern ein Laufzeitfehler ausgelöst.
Syntax
LABEL(db : INTEGER; field : INTEGER|STRING [;mode : INTEGER]) : STRING
Parameter
db : Tabellenhandle von OpenD
field : Feldnummer oder Feldbezeichner
mode : Format bei Linkfeldern: 0 = Nur Feldname, 1 = Name(Zieltabelle) (Vorgabe)
Ergebnis
Feldbezeichner
- 183 -
Betriebssystemfunktion
Beispiel: In einem Template alle Felder ersetzen
nloop(i,MaxLabel(db)-1,subst('#'+LABEL(db,i+1)+'#',db,i+1,5))
Bei Linkfeldern wird default die gesamte Feldinformation incl. den Angaben nach dem link, übergeben.
Wenn man diese nicht benötigt, sondern nur den Feldbezeichner haben möchte, so muss man mit dem Modus=0 arbeiten
19.19.1 Ergebnis von label()
Beispiel:
Gegeben ist folgende Tabellstruktur:
[STRUCTURE]
ID,STRING,80
AutoID_Tabelle,LINK,tdb7table.dat
Label(FileNr,1) liefert ID zurück
Label(FileNr,2) liefert AutoID_Tabelle(tdb7table.dat) zurück
Wenn man nur den reinen Feldbezeichner haben möchte, so muss man mit den Modus=0 arbeiten:
Label(FileNr,2,0) liefert AutoID_Tabelle zurück
19.20
Feldnummer ermitteln - labelno() – 18.11.2011
Syntax
LABELNO(db : INTEGER; field : INTEGER|STRING) : INTEGER
Parameter
db : Tabellenhandle von OpenDB
field : Feldnummer oder Feldbezeichner
Ergebnis
Feldnummer
- 184 -
Betriebssystemfunktion
Wichtiger Hinweis:
LabelNo liefert einen Wert zurück, wenn das Feld mit einer Zahl beginnt.
Aus diesem Grund sollte ein Datenfeld niemals mit einer Zahl beginnen.
If LabelNo(db,“ 19.06.2012 Termin mit Herrn Götz“)>0
Liefert als Ergebnis 19 zurück und dies ist definitiv falsch.
Prüft man auf die Existenz eines Datenfeldes und kann man nicht ausschließend, dass als Feldbezeichner inkorrekte Daten kommen, so sollte man die Prüfung
wie folgt erweitern:
If l:=LabelNo(db,sLabel),sLabel=label(db,l)
Hinweis:
Illegale db/field-Kombinationen führen zu einem Laufzeitfehler.
19.21 lastrec() - Nummer des letzten Datensatzes bzgl aktuellem Zugriff
Beschreibung
Je nachdem, wie der Zugriff mit ACCESS für die Tabelle gesetzt wurde, liefert FIRSTREC unterschiedliche Ergebnisse:
Markierung (-2): letzter Datensatz in Markierungliste
Nummer (-1) : filesize(db)
Index (0..15) : Datensatz mit der größten Indexinformation
SyntaxL
LASTREC(db : INTEGER) : INTEGER
Parameter
db : Tabellenhandle von OpenDB
Ergebnis
physikalische Satznummer des letzten Datensatzes
Beispiel: Vollständiges absteigendes Durchlaufen eines Tabelle über einen Index
db:=OpenDB(...);
access(db,1); // Zugriff auf Index setzen
x:=lastrec(db);
WHILE x DO
readrec(db,x);
- 185 -
Betriebssystemfunktion
... hier steht der Satz zur Verfügung
x:=prevrec(db)
END
19.22
link()
Syntax
LINK
19.23 linkblob() - verknüpft eine externe Datei mit einem BLOB-Feld
Beschreibung
Hier werden nur der Pfad der eingelesenen Datei und der Typ gespeichert.
Bei verwendung von eigenen Typen sehen Sie bitte auf http://www.tdb-engine.de nach, welche Typen bereits reserviert sind.
Syntax
LINKBLOB(Tabellenfeld; Pfad : STRING; Typ : INTEGER) : INTEGER
Parameter
Tabellenfeld als
Tabelle : INTEGER; Feldnummer : INTEGER
Tabelle : INTEGER; Label : STRING
Pfad : Name der externen Datei
Typ : Typkennzeichen der Datei
Folgende Typen sind reserviert:
1 : BMP (Windows Bitmap)
2 : PCX (PCX-Bitmap)
3 : WAV (Sounddatei)
4 : WMF (Windows Metafile)
5 : GIF (Image)
6 : JPG (Image)
Ergebnis
-1 : Operation erfolglos (illegaler Pfad etc.)
ansonsten : Größe des Blobs in Bytes
- 186 -
Betriebssystemfunktion
19.24
linkinfo()
Syntax
LINKINFO(db : INTEGER) : STRING
19.25 lock() - Tabellensperre einrichten
Beschreibung
Die Funktion Lock dient dem Setzen der elementaren Schreib- beziehungsweise Totalsperre. Lock sollte immer dann benutzt werden, wenn Zugriffe auf
Datenbankdateien über mehrere einzelne prgs erfolgen, die ihrerseits wiederum Sperren setzen und wieder aufheben (z.B. WriteRec). Durch Lock ist es nun
möglich, zu Beginn der komplexen Datenbankzugriffe einmal die erforderliche Sperre einzurichten, die prgs abzuarbeiten und die Sperre mit Unlock wieder
aufzuheben. Dies bringt im Netz nicht nur eine erhöhte Sicherheit, sondern auch eine gesteigerte Performance.
Hinweis: Jede erfolgreich eingerichtete Sperre muss mit Unlock wieder aufgehoben werden.
Das folgende Beispiel zeigt bereits den Sinn von Lock. Unter Umständen muß ein gesicherter Datensatz wieder gelesen werden. Im ungünstigsten Fall wird
zwischen dem Sichern der Satznummer in nSaveRec und dem Zurücksetzen des Satzzeigers mit ReadRec(KUNDE, nSaveRec) der Datensatz von einem
anderen prg entfernt.
Somit wäre ein sicheres Lesen des alten Datensatzes gefährdet. Daher sollten solche Vorgänge immer von Lock und Unlock umgeben sein.
Syntax
LOCK(db : INTEGER; Sperre : INTEGER; Modus : INTEGER) : INTEGER
Parameter
db : Tabellenhandle
Sperre:
0 : Schreibsperre
1 : Totalsperre
Modus:
0 : Sperre wird unbedingt eingerichtet; Programm wartet
1 : Sperre wird eingerichtet, wenn möglich; Programm wartet nicht
Ergebnis
1 : Sperre ist eingerichtet
0 : Sperre ist nicht eingerichtet
Beispiel 1: Lock
PROCEDURE FindName(cSuch: String): INTEGER;
- 187 -
Betriebssystemfunktion
VAR nSaveRec, nret, KUNDE : INTEGER;
KUNDE:=OpenDB('kunde.dat');
Lock(KUNDE, 0);
nSaveRec := RecNo(KUNDE);
ReadRec(KUNDE, FINDREC(KUNDE, cSuch));
IF NOT GetField(KUNDE,'Nachname') LIKE cSuch
ReadRec(KUNDE, nSaveRec);
ELSE
nRet := 1;
END
UnLock(KUNDE, 0);
Return nRet
ENDPROC;
Beispiel 2: Lock
IF Lock(ARTIKEL,1,1)
x:=FindRec(ARTIKEL,"12345","ARTIKEL.ID",1)
ReadRec(ARTIKEL,x)
SetRField(ARTIKEL,"Preis",GetRField(ARTIKEL,"Preis")*1.05)
WriteRec(ARTIKEL,x)
UnLock(ARTIKEL,1)
ELSE
CGIWriteLn("Hinweis: ARTIKEL kann momentan nicht gesperrt werden")
END
- 188 -
Betriebssystemfunktion
Beispiel 3: Lock
WHILE NOT Lock(ARTIKEL,1,1)
Pause(10)
END
..Hier ist die Tabelle gesperrt
x:=FindRec(ARTIKEL,"12345","ARTIKEL.ID",1)
ReadRec(ARTIKEL,x)
SetRField(ARTIKEL,"Preis",GetRField(ARTIKEL,"Preis")*1.05)
WriteRec(ARTIKEL,x)
Unlock(ARTIKEL)
19.26 maxfile() - größter gültiger Tabellenhandle
Beschreibung
Durch OpenDB werden fortlaufende Tabellenhandles vergeben. Durch CloseDB werden die Tabellenhandles wieder freigegeben. MAXFILE wird jedoch nur dann
durch CloseDB reduziert, wenn die Tabellen mit den höchsten Handles geschlossen werden.
Syntax
MAXFILE : INTEGER
Parameter:
Ergebnis größter gültiger Tabellenhandle
Beispiel.Alle Tabellen schließen
nloop(i,MAXFILE-1, CloseDB(i+1))
19.27
maxlabel() - Anzahl der Datenfelder (Spalten) in einer Tabelle
Syntax
MAXLABEL(db : INTEGER) : INTEGER
Parameter
- 189 -
Betriebssystemfunktion
db : Tabellenhandle von OpenDB
Ergebnis
Anzahl der Felder (Spalten)
- 190 -
Betriebssystemfunktion
Beispiel: Struktur einer Tabelle ausgeben
VAR i, db : INTEGER;
...
i:=0;
WHILE i++<=MAXLABEL(db) DO
cgiwriteln(GetStructure(db,i))
END;
19.28 memolen() - Anzahl der Zeichen in einem Memo-Feld ermitteln
Beschreibung
Falls mit dem Feld kein Memo-Feld bezeichnet wird, erzeugt die tdbengine einen Laufzeitfehler. Andernfalls wird die Anzahl aller Zeichen im Memo
zurückgeliefert (inkl. aller unsichtbaren Zeichen).
Syntax
MEMOLEN(db : INTEGER; field : INTEGER|STRING) : INTEGER
Parameter
db : Tabellenhandle von OpenDB
field: Feldnummer oder Feldbezeichner (als String)
Ergebnis
Anzahl der Zeichen im Memo
19.29 memostr() - Anfang eines Memo-Feldes als String
Beschreibung
Wenn kein gültiges Memo-Feld angegeben wird, wird ein Laufzeitfehler ausgelöst.
Zeilenumbrüche werden als Leerzeichen zurückgeliefert.
Wenn das Memo mehr als 255 Zeichen umfasst, werden nur 252 Zeichen geliefert, gefolgt von '...'
Syntax
MEMOSTR(db : INTEGER; field : INTEGER|STRING) : STRING
Parameter
db : Tabellenhandle von OpenDB
- 191 -
Betriebssystemfunktion
field : Feldnummer oder Feldbezeichner (als String)
Ergebnis
Die ersten (max 255) Zeichen des Memofeldes.
19.30 newtable() - Kopie (leer) einer Tabelle erzeugen
Beschreibung
Die neu erzeugte Tabelle hat die gleiche Struktur wie db und ist leer. Paßwort und Verschlüsselungscode werden ebenfalls von db übernommen.
Syntax
NEWTABLE(db : INTEGER; fn : STRING [; Auto-Start : INTEGER]) : INTEGER
Parameter
db : Tabellenhandle von OpenDB
fn : Pfad zur neuen Tabelle
Auto-Start : Startwert für das AUTO-INCREMENT-Feld (falls vorhanden)
Ergebnis
0 : Datei wurde erfolgreich erzeugt
sonst: Fehlercode
19.31 nextmark() - nächster markierter Datensatz
Beschreibung
FIRSTMARK und NEXTMARK werden verwendet, um die Markierungsliste einer Tabelle durchzugehen:
...hier sind Sätze markiert
x:=FIRSTMARK(db); // am Anfang positioniert
WHILE x DO
readrec(db,x);
... hier wird mit dem Datensatz gearbeitet
x:=NEXTREC(db,x)
END
Syntax
NEXTMARK(db : INTEGER; prev_mark : INTEGER) : INTEGER
- 192 -
Betriebssystemfunktion
Parameter
db : Tabellenhandle von OpenDB
prev_mark : physikalische Satznummer eines Datensatzes aus der Markierungsliste
Ergebnis
0 : Kein (weiterer) Datensatz in der Markierungsliste
sonst: physikalische Satznummer des auf prev_mark folgenden Satzes in der Markierungsliste
19.32 nextrec() - Physikalische Satznummer des Nachfolgers
Beschreibung
Die Funktionen FIRSTREC, LASTREC, PREVREC und NEXTREC beziehen sich immer auf die aktuelle Ordnung, die mit der Funktion ACCESS eingestellt wird.
Folgende Ordnungen (Zugriffsreihenfolgen) sind möglich:
Nummer -> physikalische Satznummer
Markierung -> interne Markierungsliste
Index -> Reihenfolge wird aus dem Index bestimmt
Eine typische Durchlaufsequenz schaut so aus:
x:=FIRSREC(tabelle)
WHILE x DO
READREC(tabelle,x)
... mit dem Datensatz arbeiten
x:=NEXTREC(tabelle)
END
Hinweis: Die Funktionen aus dieser Gruppe sollte man nur im Zusammenhang mit Index-Zugriffen verwenden. Für den Zugriff über physikalische Satznummern
oder Markierungen gibt es schnellere Möglichkeiten:
Nummer:
x:=0; WHILE x++<=FILESIZE(tabelle) DO ... END
- 193 -
Betriebssystemfunktion
Markierung:
x:=FIRSTMARK(tabelle); WHILE x DO ...; x:=NEXTMARK(tabelle,x) END
Syntax
NEXTREC(Tabellenhandle : INTEGER) : INTEGER
Parameter
Tabellenhandle von OPENDB
Ergebnis
Pysikalische Satznummer des Nachfolgers
0, wenn kein Vorgänger vorhanden
19.33 nmarks() - Größe der Markierungsliste eines Tabellenhandle
Beschreibung
Jeder von OPENDB gelieferte Tabellenhandle beinhaltet eine Liste, in die beliebig viele Zeilenreferenzen (Satznummern) aufgenommen werden können. Jede
Zeilenreferenz kann allerdings nur einmal aufgenommen werden. Es handelt sich demnach um eine geordnete Teilmenge aus der Tabelle.
NMAKRS liefert die Größe dieser Liste
Syntax
NMARKS(Tabellenhandle : INTEGER) : INTEGER
Parameter
Tabellenhandle von OPENDB
Ergebnis
Anzahl der Einträge in der Markierungsliste
- 194 -
Betriebssystemfunktion
19.34 prevrec() - Physikalische Satznummer des Vorgängers
Beschreibung
Die Funktionen FIRSTREC, LASTREC, PREVREC und NEXTREC beziehen sich immer auf die aktuelle Ordnung, die mit der Funktion ACCESS eingestellt wird.
Folgende Ordnungen (Zugriffsreihenfolgen) sind möglich:
Nummer -> physikalische Satznummer
Markierung -> interne Markierungsliste
Index -> Reihenfolge wird aus dem Index bestimmt
Eine typische Durchlaufsequenz schaut so aus:
x:=LASTREC(tabelle)
WHILE x DO
READREC(tabelle,x)
... mit dem Datensatz arbeiten
x:=PREVREC(tabelle)
END
Hinweis: Die Funktionen aus dieser Gruppe sollte man nur im Zusammenhang mit Index-Zugriffen verwenden. Für den Zugriff über physikalische Satznummern
oder Markierungen gibt es schnellere Möglichkeiten:
Nummer:
x:=0; WHILE x++<=FILESIZE(tabelle) DO ... END
Markierung:
x:=FIRSTMARK(tabelle); WHILE x DO ...; x:=NEXTMARK(tabelle,x) END
Syntax
PREVREC(Tabellenhandle : INTEGER) : INTEGER
Parameter
Tabellenhandle von OPENDB
Ergebnis
Pysikalische Satznummer des Vorgängers
0, wenn kein Vorgänger vorhanden
- 195 -
Betriebssystemfunktion
19.35 putmarks() - Setzen der Markierungsliste einer Tabelle
Beschreibung
Zunächst wird eine eventuell bestehende Markierungsliste von DB gelöscht. Dann werden die Markierungen entsprechend Marks gesetzt. Ist Marks vom Typ
TMarks, so werden die bei GETMARKS gespeicherten Markierungen zurückgeschrieben.
Ist Marks vom Typ TBITS[], so wird x markiert, wenn Marks[x] den Wert 1 hat:
VAR i : INTEGER
WHILE i++<=FileSize(DB)
DO
IF Marks[i] THEN
SetMark(DB,i)
END
END
Syntax
PUTMARKS(DB : INTEGER; Marks) : INTEGER
Parameter
db : Tabelenhandle von OPENDB
Marks : TMARKS oder TBITS[]
Ergebnis
Kardinalität der Markierungsliste (Anzahl der markierten Datensätze)
19.36
putrec() - Schreibt ein Array in den Satzpuffer
Syntax
PutRec(db : INTEGER; VAR buffer : BYTE[])
Parameter
db : Tabellenhandle von OpenDB
buffer : Puffer
Beispiel, wie man einen Datensatz wieder in den ursprünglichen Zustand zurückversetzt.
- 196 -
Betriebssystemfunktion
Var buffer : Byte[]
VAR A : Integer = opendb(„adressen.dat”)
Readrec(A, findrec(A,”tdb”,”$Name”,1,1))
.. Satzbuffer wird in buffer abgelegt
Getrec(A,buffer)
..Die Feldeinträge werden gelöscht
Setfield(A,”Name”, „“)
Setfield(A,”Vorname”,””)
.. und mit Putrec wieder zurückgeholt
Putrec(A,buffer)
Writerec(A,recnr(A))
- 197 -
Betriebssystemfunktion
19.37 readrec() - Liest einen Datensatz
Beschreibung
Jede geöffnete Tabelle verfügt über einen Puffer für genau einen Datenstz dieser Tabelle.
Mit READREC wird ein Datensatz aus der Tabelle (die immer auf einem externen Datenträger vorliegt) in den Satzpuffer übertragen
Syntax
READREC(DB : INTEGER; recno : INTEGER) : INTEGER
Parameter
DB : Tabellenhandle von OPENDB
recno : physikalische Satznummer (Position)
Ergebnis
recno
19.38 recno() - Nummer des aktuellen Datensatzes einer Tabelle
Beschreibung
Die physikalische Satznummer wird beispielsweise durch READREC(db,x) auf den Wert x gesetzt. Markierungen und Indizes liefern letztlich immer physikalische
Satznummern.
Die tdbengine arbeitet mit einem Satzpuffer pro Tabelle. Die Übertragung zwischen Tabelle und Satzpuffer geschieht mit den Funktionen READREC und
WRITEREC, die beide eine physikalische Satznumer (die Position innerhalb der Tabelle) als Parameter haben. Nur mit diesen beiden Funktionen wird die
Satznummer des Puffers direkt gesetzt, die dann mit RECNO geliefert wird.
Nach dem Öffnen einen nichtleeren Tabelle gilt RECNO(DB) = 1
Beim Löschen eines Datensatzes mit DELREC wird auf dem nächsten (bzgl. des aktuellen Zugriffes) Datensatz positioniert. Entsrechend liefert RECNO dann die
entsprechende Satznummer.
Syntax
RECNO(db : INTEGER) : INTEGER
Parameter
db : Tabellenhandle von OPENDB
Ergebnis
physikalische Nummer des aktuellen Datensatzes
- 198 -
Betriebssystemfunktion
19.39 recnr() - Nummer des aktuellen Datensatzes einer Tabelle (veraltet)
Beschreibung
Die physikalische Satznummer wird beispielsweise durch READREC(db,x) auf den Wert x gesetzt. Markierungen und Indizes liefern letztlich immer physikalische
Satznummern.
Syntax
RECNR(db : INTEGER) : INTEGER
Parameter
db : Tabellenhandle von OPENDB
Ergebnis
physikalische Nummer des aktuellen Datensatzes
19.40 setauto() - Setzen der nächsten AUTO-Nummer
Beschreibung
Mit dieser Funktion kann ein neuer Startwert für die automatische Nummerierung vergeben werden.Es ist darauf zu achten, dass n größer als die höchste bisher
vergebene Nummer ist, da andernfalls doppelte Autonummern in der Tabelle vorkommen können.Wird die Funktion mit Paramter n=0 aufgerufen, so wird
diejenige Nummer geliefert, die der nächste neue Datensatz erhalten wird.
Syntax
SETAUTO(DB [,n] : INTEGER) : INTEGER
Parameter
DB : Tabellenhandle von OPENDB
n : Ganze Zahl
Ergebnis
Nächste Autonummer, die beim Anfügen eines neuen Datensatzes vergeben wird.
19.41 setfield() - Übertragung von Informationen in den Satzpuffer
Beschreibung
Der Inhalt von cont wird in des angegebene Feld des Satzpuffers übertragen. Dabei findet eine Typkonvertierung statt. Kann die Konvertierung nicht durchgeführt
werden ('1,00' -> NUMBER), so wird ein Laufzeitfehler ausgelöst.SETFIELD kann nicht verwendet werden für MEMOS (stattdessen READMEMO)
BLOBS (stattdessen LINKBLOB oder EMBEDBLOB).Bei der Zuweisung von numerischen Felden ist SETRFIELD vorzuziehen.
Syntax
- 199 -
Betriebssystemfunktion
SETFIELD(db : INTEGER; field : INTEGER|STRING; cont : STRING[; VAR buffer : BYTE[]]) : STRING
Parameter
db : Tabellenhandle von OpenDB
field : Feldnummer order Feldbezeichner
cont : Zeichenkette
buffer : Puffer
Ergebnis
cont
- 200 -
Betriebssystemfunktion
19.42 setfields() - mehrere Felder eines Datensatzes belegen
Beschreibung
Die einzelnen Datenfelder werden entsprechend gesetzt.
Syntax
SETFIELDS(db : INTEGER; replacestr : STRING) : INTEGER
Parameter
db : Tabellenhandle von OpenDB
replacestr: String der Form "field_1:=value_1, field_2:=value_2..."
Ergebnis
0 : kein Fehler aufgetreten
sonst : Fehlercode
19.43 setfilter() - Setzt einen Filter auf den aktiven Index
Beschreibung
SetFilter ist nur wirksam, wenn der Zugriff auf einen Index gesetzt wurde, also erst nach access(db,...).
Ein Filter ist solange aktiv, bis ermit SetFilter(db,'') aufgehoben wird, oder die Primärtabelle gewechselt wird.
Ist ein Filter aktiv, führt FirstRec ein FindRec(db,von) aus, liefert aber nur einen Wert, wenn der gefundene Datensatz gleich oder größer dem Inhalt von "von" ist.
Entsprechendes gilt für LastRec. NextRec bzw PrevRec liefern nur dann Ergebnisse, wenn die gefundenen Werte im aufgespannten Bereich liegen. Auch SUB
liefert nur Datensätze aus diesem Bereich.
Der Parameter bis kann nur weggelassen werden, wenn die zugehörige Indexbeschreibung ausschließlich String-Felder enthält:
SetFilter(db,von) steht dann als Abkürzung für SetFilter(db,von,von+chr(255)).
Bei einem hierarchischen Index werden nicht angegebene Komponenten bei von mit dem kleinsten, bei bis mit dem größten möglichen Wert aufgefüllt.
Syntax
SetFilter(db : INTEGER; von [, bis] : STRING[; mark : INTEGER]) : INTEGER
Parameter
db : Tabellenhandle von OpenDB
von, bis : Indexinformation gemäß Indexbeschreibung
mark: 1 -> Der gesamte Filterbereich wird markiert
- 201 -
Betriebssystemfunktion
Ergebnis
Die Anzahl der markierten Datensätze
19.43.1 Filteranweisungen
Die Filteranweisung ist von Feldtyp des Feldes abhängig, welcher im Index verwendet wird. Die Filteranweisung ist als String zu übergeben.
Feldtyp
String
Datum
Unix Time
Select
Format
Test
tt.mm.jjjj
tt.mm.jjjj_hh:mm:ss
Ganzzahl
Time
Linkfeld
hh:mm
Ganzahl
Beispiel Definition
Name,String,30
Termin,date
Beginn,utime
Schweregrad,select,hoch,mitt
el,niedrig
Uhrzeit,time
AutoID_Mitarbeiter
Beispiel
Hermann
31.12.2010
31.12.2010_12:00:00
1
12:00
4711
19.43.2 Filter mit gleichzeitigem Markieren der Datensätze
Der letzte Parameter ermöglicht die sofortige Markierung der mit setFilter() eingegrenzten Datensätze. Damit kann man den Filtervorgang und das Markieren der
Datensätze in einem Vorgang beschleunigen.
Beispiel:
SetFIlter() bei einem hierarchisch Index
Voraussetzung zu unserem Beispiel ist der folgende Indexaufbau:
index_3=einsatz3.ind:$AutoID_Bereitschaftsgruppe,$Termin
PrimTable(E); Access(E,"einsatz3.ind")
SetFilter(E, GetField(G,"AutoID")+‘,‘+ DateStr(iV),GetField(G,"AutoID")+‘,‘+ DateStr(iB))
i := FirstRec(E)
While i
ReadRec(E,i)
SetRField(E,"AutoID_Bereitschaftsgruppe",0)
SetRField(E,"AutoID_Bereitschaft",0)
WriteRec(E, i)
i := NextRec(E)
End
- 202 -
Betriebssystemfunktion
SetFilter(E,"")
Hinweis:
SetFilter greift bei einem hierarchischen Index nur dann, wenn sich der letzte Wert in dem Index ändert. Wäre der Index wie folgt aufgebaut:
index_3=einsatz3.ind: $Termin ,$AutoID_Bereitschaftsgruppe
und würde man die setfilter-Anweisung so definieren
PrimTable(E); Access(E,"einsatz3.ind"); SetFilter(E,DateStr(iV)+','+GetField(G,"AutoID"),DateStr(iB)+','+GetField(G,"AutoID"))
Dann kann setFilter kein korrektes Ergebnis liefern. Hier ändert sich das Datum und setfilter liefert alle Datumseinträge zwischen iV und iB als Treffer zurück.
19.43.3
Häufige Programmierfehler im Zusammenhang mit setfilter()
Programmierung einer endlosen-Schleife
Der folgende Code produziert eine Endlosschleife, da innerhalb der Schleife auf ein Feld geschrieben wird, welches im Index enthalten ist. Damit rutscht der
Datensatz physikalisch an eine andere Position im Index und der ordnungsgemäße Durchlauf mit nextrec ist nicht mehr gewährleistet.
PrimTable(E);Access(E,"einsatz3.ind")
SetFilter(E,GetField(G,"AutoID")+','+DateStr(iV),GetField(G,"AutoID")+','+DateStr(iB)) i := FirstRec(E)
While ReadRec(E,i)
i := NextRec(E)
SetRField(E,"AutoID_Bereitschaftsgruppe",0)
SetRField(E,"AutoID_Bereitschaft",0)
WriteRec(E, RecNr(E))
End
SetFilter(E,"")
- 203 -
Betriebssystemfunktion
Besser ist folgende Lösung
PrimTable(E); Access(E,"einsatz3.ind")
SetFilter(E,GetField(G,"AutoID")+','+DateStr(iV),GetField(G,"AutoID")+','+DateStr(iB),1)
InitArray(ei[NMarks(E)]); GetMarksAuto(E,ei)
i := -1
While ei[i++]
ReadRec(E,FindAuto(E,ei[i]))
SetRField(E,"AutoID_Bereitschaftsgruppe",0)
SetRField(E,"AutoID_Bereitschaft",0)
WriteRec(E, RecNr(E))
End
SetFilter(E,"")
19.43.4
Risikobewertung bei der Verwendung von setfilter
SetFilter ist hochperformant und mit folgender Ausnahme völlig unkritisch.
Process
Step/Input
Potential
Failure
Mode
Potential
Failure
Effects
Potential
Causes
Current
Control
Actions
Recommen
ded
Sev1
Det2
Occ3
Auf eine
SetFilterAnweisung
folgt eine
FindAndMark
–Anweisung
Kein Eintrag
im error.log
Endless lope,
der Prozess
wird am
Server nicht
mehr beendet.
Die setFilterAnweisung
liefert als
Ergebnis
keinen
Datensatz
Programmiere
r muss aktiv
den Fehler
verhindern
Vor dem
Aufruf
FindAndMark
muss
unbedingt die
letzte setfilterAnweisung
wieder
aufgehoben
werden.
Setfilter(db,““)
5
1
4
1 Severity
1=leicht, 5 =schwerwiegend
Wahrscheinlichkeit, dass der Fehler entdeckt wird 1=unwahrscheinlich, 2=möglicherweise,3= wahrscheinlich,4=sicher
3 occurrence probability= Eintrittswahrscheinlichkeit 1=unwahrscheinlich, 2=möglicherweise,3= wahrscheinlich,4=sicher
2 Detection
- 204 -
Betriebssystemfunktion
19.44 setrecord() - Kopiert einen Datensatz aus einer anderen Tabelle
Beschreibung
Wenn ein ungültiger Tabellenhandle verwendet wird, wird ein Laufzeitfehler ausgelöst.
Die Übernahme der einzelnen Felder erfolgt über die Feldbezeichner, bei mehreren gleichen Feldbezeichnern in einer Tabelle wird auch die Richtung
berücksichtigt. D.h.: Das erste Feld mit dem Label x der Tabellen db_from wird auch dem ersten Feld mit dem Label x der Tabelle db_to zugeordnet.
Typen werden weitgehend umgewandelt:
MEMO -> STRING (nicht möglich)
STRING -> MEMO (möglich)
Durch SETRECORD werden auch Inhalte von AUTO-INCREMENT-Feldern übernommen. Ob dann eine neue Nummer generiert wird, hängt vomModus bei
WRITEREC ab.
Besonderheit.
SETRECORD legt in der Zieltabelle MEMO- und BLOB-Einträge an. Wenn der Datensatz in der Zieltabelle anschließend nicht geschrieben wird, sind diese
Informationen zwar in den MMO und/oder BLB-Dateien enthalten, aber nicht mehr ansprechbar.Es ist auch möglich, dass SETRECORD mit einer Tabelle auf
sich selbst angewandt wird. In diesem Fall wird eine Kopie eines Datensatzes mit MEMO- und BLOB-Informationen erzeugt, die dann mit
WriteRec(db,filesize(db)+1) geschrieben werden kann. Diese Vorgehensweise ist immer dann angesagt, wenn ein Datensatz mit MEMO- und/oder BLOBFeldern kopiert werden soll. Andernfalls werden die Zeiger in die MEMO- und/oder BLOB-Datei kopiert, was zu deren Zerstörung führen kann!
Syntax
SETRECORD(db_from, db_to : INTEGER[; mode: INTEGER]) : INTEGER
Parameter
db_from, db_to : Tabellenhandle von OpenDB
mode : 0 -> Alle Felder überschreiben (Vorgabe)
1 : Es werden nur Felder überschrieben, die in db_to leer sind. Leer bedeutet
'' bei Strings
0 bei allen Zahlen, Datum, etc
NEIN bei BOOLEAN
Memos und Blob haben keine Verweis.
Ergebnis
Immer 0
- 205 -
Betriebssystemfunktion
19.45 setrfield() - Übertragung von numerischen Informationen in den Satzpuffer
Beschreibung
Der Inhalt von cont wird in des angegebene Feld des Satzpuffers übertragen. Dabei findet eine Typkonvertierung in den Ergebnistyp (Feldtyp) statt.
SETRFIELD kann nicht verwendet werden für
MEMOS (statt dessen READMEMO)
BLOBS (statt dessen LINKBLOB oder EMBEDBLOB)
Syntax
SETRFIELD(db : INTEGER; field : INTEGER|STRING; cont : INTEGER) : INTEGER
Parameter
db : Tabellenhandle von OpenDB
field : Feldnummer order Feldbezeichner
cont : INTEGER
Ergebnis
cont
19.46 sortmark() - sortiert die Markierungsliste
Beschreibung
Eine Index-Definition hat folgende Struktur:
Hierarchischer Index::=Feld-Def{","Feld-Def}.
Feld-Def::=["$"]Feld[":"Zahl]
Feld::=Feldbezeichner|Feldnummer
Berechneter Index::="("Ausdruck")"[":"Zahl]
Beispiele:
Hierarchischer Index: Name,Vorname:1,PLZ
Berechneter Index: (Name+'/'+Vorname):40
Die optionale Angabe :Zahl nach einem Feld (bzw. nach einem Ausdruck) wird nur bei String-Feldern (bzw. bei String-Ausdrücken) ausgewertet und gibt an,
wieviele Zeichen des Strings (maximal) in die Indexinformation übernommen werden.
- 206 -
Betriebssystemfunktion
Folgende Einschränkungen für Indexbeschreibungen gelten derzeit (Version 6.2.6):
Der Ausdruck eines berechneten Index darf derzeit 40 Zeichen nicht überschreiten.
Die Indexinformation (Summe aller Hierarchiestufen) darf maximal 256 Bytes umfassen.
Syntax
SORTMARK(db : INTEGER; IndDef : STRING) : INTEGER
Parameter
db : Tabellenhandle von OpenDB
IndDef : Index-Definition
Ergebnis
1 : erfolgreich sortiert
0 : es ist ein Fehler aufgetreten
19.47 unlock() - Sperre freigeben
Beschreibung
Hebt eine mit Lock gesetzte Sperre wieder auf.
Syntax
UNLOCK(db : INTEGER; Sperre : INTEGER) : INTEGER
Parameter
db : Tabellenhandle
Sperre:
0 : Schreibsperre
1 : Totalsperre
19.48 Schreibt einen Datensatz - writerec() - 05.12.2011
Beschreibung
Gültige Werte für recno sind 1 bis FileSize(DB)+1. Ist recno kleiner oder gleich FileSize(DB), so wird der an dieser Position stehende Datensatz überschrieben.
Ist recno=FileSize(DB)+1, so wird ein neuer Datensatz an die Tabelle angehängt.
Syntax
WRITEREC(DB : INTEGER; recno : INTEGER [; mode : INTEGER]) : INTEGER
Parameter DB : Tabellenhandle von OPENDB
- 207 -
Betriebssystemfunktion
recno : physikalische Satznummer (Position)
mode : 0 = Gepuffertes Schreiben, FlushDB erforderlich;
1 = Ungepuffert (Vorgabe)
Ergebnis
recno (Satzposition nach Ausführung der Operation)
Hinweis:
Beim gepufferten Schreiben wird die Datei und die Indexdateien erst beim Aufruf von Flushdb geschrieben.
Ohne flushdb bleibt die Tabelle völlig leer.
Bei großen Mengen an Daten, z.B. während eines Imports sollte man des öfteren zwischendrin flushdb ausführen.
Zum Beispiel jeweils nach 100.000 Records.
Hier muss man sich etwas herantasten und prüfen, wieviele Einträge im Speicher gehalten werden können.
Wenn der Speicher ausgeht, bricht der Lauf ab und man hat einen undefinierten Datenstand.
19.48.1
Beispielprogramm für gepuffertes Speichern
Procedure Main
Var i : Integer
Var AQ : Integer = OpenDB("../database/i_address.dat","",0,15)
Var Q : Integer = OpenDB("../database/dellivery_cards.dat","",0,15)
ClearDat(AQ);RegenAll(AQ)
i := FirstRec(Q)
While ReadRec(Q,i)
ReadRec(AQ,0)
SetField(AQ,"AutoID_Quelle",GetField(Q,"AutoID"))
SetField(AQ,"Name2",GetField(Q,"name2"))
SetField(AQ,"Name",GetField(Q,"end_user_name"))
SetField(AQ,"Postcode",GetField(Q,"end_user_zip"))
SetField(AQ,"Street",GetField(Q,"end_user_address"))
SetField(AQ,"City",GetField(Q,"end_user_city"))
WriteRec(AQ,FileSize(AQ)+1)
i := NextRec(Q)
End
FlushDB(AQ)
CloseDB(AQ);CloseDB(Q)
endproc
- 208 -
Betriebssystemfunktion
Abbildung 1:Beispielprogramm für gepuffertes Schreiben
und hier das gleiche Programm, nur dass flushdb zyklisch aufgerufen wird
Procedure Main
Var z,i : Integer
Var AQ : Integer = OpenDB("../database/i_address.dat","",0,15)
Var Q : Integer = OpenDB("../database/dellivery_cards.dat","",0,15)
ClearDat(AQ);RegenAll(AQ)
i := FirstRec(Q)
While ReadRec(Q,i)
ReadRec(AQ,0)
SetField(AQ,"AutoID_Quelle",GetField(Q,"AutoID"))
SetField(AQ,"Name2",GetField(Q,"name2"))
SetField(AQ,"Name",GetField(Q,"end_user_name"))
SetField(AQ,"Postcode",GetField(Q,"end_user_zip"))
SetField(AQ,"Street",GetField(Q,"end_user_address"))
SetField(AQ,"City",GetField(Q,"end_user_city"))
WriteRec(AQ,FileSize(AQ)+1)
i := NextRec(Q)
if z++ > 10000
FlushDB(AQ)
z:=0
end
end
FlushDB(AQ)
CloseDB(AQ);CloseDB(Q)
endproc
Abbildung 2:Beispielprogramm 2 für gepuffertes Schreiben
Warnung!
Wenn man mehrere WriteRec hintereinander stehen hat und hier einmal mit dem letzten Parameter 0 und ohne letzten Parameter arbeitet, dann ist die Tabelle
defekt. Dies ist ersichtlich, weil dann Löchern in der fortlaufenden Nummerierung [~AutoID) entstehen.
Die Indexdateien sind dann auch kaputt.
- 209 -
Betriebssystemfunktion
Die Datei sieht dann so aus:
In diesem Fall ist es das Beste, die Datei komplett neu aufzubauen.
19.49 flushdb() - Leert den Schreibpuffer
Beschreibung
Wird mit WRITEREC(db,x,0) gepuffert in eine Tabelle geschrieben, muss am Ende der Schreiboperationen FLUSHDB aufgerufen werden, damit die Änderungen
sicher übertragen werden. Vor allem, wenn neue Datensätze angefügt werden, sind ohne FLUSHDB die Sätze nicht gespeichert. Gepuffertes Schreiben ist
normalerweise nicht notwendig, kann aber bei sehr vielen Schreiboperationen (Import grosser Datenbestände) die Performance leicht erhöhen.
Syntax
FLUSHDB(Tabellenhandle : INTEGER) : INTEGER
Parameter
Tabellenhandle von OPENDB
- 210 -
Betriebssystemfunktion
Ergebnis
0 (kein Rückgabewert)
19.50 flush() - Übertragung sämtlicher gepufferter Daten
Beschreibung
Der Aufruf von FLUSH bewirkt, dass sämtliche File-Handles (vorübergehend) geschlossen werden, nachdem eventuell gespeicherte Daten auf den Datenträger
übertragen werden.
Zudem wird der Cache-Speicher für die BTrees der Indizes freigegeben.
Durch Flush wird sichergestellt, dass das Betriebssystem den aktuellen Tabellenzustand erhält und damit an andere Anwendungen übermitteln kann.
Syntax
FLUSH
- 211 -
Betriebssystemfunktion
20.
Betriebssystemfunktionen
BaseDir
ChDir
CopyFile
DBDir
DelFile
DiskFree
FirstDir
Free
GetDir
GetEnv
IsFile
MakeDir
NextDir
nHandles
PrivDir
RemDir
20.1
20.1.1
Projektverzeichnis ????
Verzeichniswechsel
Kopiert eine Datei analog zur DOS-Funktion copy
Verzeichnis zur einer Tabelle ermitteln
Datei löschen
Freien Platz auf der Festplatte ermitteln.
Ersten Verzeichniseintrag suchen
Freien Speicher ermitteln
Verzeichnisnamen bestimmen
Umgebungsvariable abfragen
Prüft, ob Datei existiert
Verzeichnis anlegen
Nächsten Verzeichniseintrag suchen
Anzahl der noch freien Handles
Rückgabe des privaten Verzeichnisses
Verzeichnis löschen
Zugriff auf Verzeichnisse
Aktuelles Verzeichnis ermitteln - GetDir
20.1.2
chmod() - Dateirechte ändern
Beschreibung
Die Funktion setzt neu unter Linux die Rechte an der Datei, je nach den Rechten,die mit dieser Funktion ausgeführt werden (also nur als root bzw Besitzer der
Datei). Die neuen Rechte werden als 'rwxrwxrwx' angegeben, wobei das erste Trippel für die Rechte des Benutzers, das zweite für die Rechte der Gruppe und
das letzte für die Rechte des Rests der Welt gilt. Das Funktionsergebnis liefert den Linux-Fehlerstatus.
Syntax
CHMOD(Pfad,Rechte : STRING) : INTEGER
Parameter
Pfad : Datei, deren Rechte geändert werden sollen
- 212 -
Betriebssystemfunktion
Rechte: String der Form 'rwxrwxrwx'
Ergebnis
Fehlercode des Betriebssystems
20.1.3
Verzeichnis einer TDB-Tabelle ermitteln - DBDir
DBDir(Tabelle): String;
Aufgabe
Rückgabewert
20.1.4
Ermittlung eines Verzeichnisses
Liefert das Verzeichnis einer Tabellen-Datei.
Projektverzeichnis ermitteln – BaseDir ??
BaseDir: String;
Message("Aktuelles Verzeichnis :"+BaseDir)
20.1.5
Verzeichnis anlegen - MakeDir
MakeDir (Zeichenkette)
Die Zeichenkette muß ein dem Betriebssystem entsprechender Verzeichnisname sein. Es wird ein Verzeichnis mit diesem Namen neu angelegt.
Ergebnis:
0
Verzeichnis erfolgreich angelegt
1
Verzeichnis existiert bereits
2
Verzeichnis kann nicht angelegt werden
Beispiel:
IF MakeDir("D:\TEXTE")=1
Message("Verzeichnis existiert bereits","Achtung",1)
END
20.1.6
Verzeichnis löschen – RemDir ???
RemDir (Zeichenkette)
Entfernt einen Verzeichniseintrag
Ergebnis:
0
Verzeichnis wurde entfernt
1
Verzeichnis ist nicht leer
2
Verzeichnis existiert nicht
- 213 -
Betriebssystemfunktion
Beispiel:
IF RemDir("D:\TEXTE")=1
Message("Verzeichnis kann nicht gelöscht werden","Achtung",1)
END
20.1.7
Verzeichnis auf Existenz abprüfen
Für die Prüfung, ob ein Verzeichnis existiert, gibt es keine eigene Funktion. Hier muß man sich mit folgender Anweisung behelfen:
Var cTest : STRING
cTest := "C:\WINTDB"
IF MakeDir( cTest)#1
ELSE
cgiwriteln("Verzeichnis existiert")
END
20.2
20.2.1
Dateioperationen
Datei kopieren – CopyFile()
CopyFile (Quelldatei,Zieldatei): REAL
analog zur DOS-Funktion COPY kann man mit CopyFile Dateien kopieren
Der Rückgabewert ist immer 256
20.2.2
Datei löschen – DelFile()
DelFile (Zeichenkette)
Die Datei mit dem angegeben Namen wird gelöscht. Das Ergebnis der Funktion ist 0, wenn die Operation erfolgreich ausgeführt werden konnte, andernfalls wird
der Fehlerstsatus zurückgeliefert.
20.2.3
Datei auf Existenz prüfen – IsFile()
IsFile (Zeichenkette)
Die Zeichenkette bestimmt einen Dateinamen. Die Funktion liefert 1, falls eine Datei dieses Namens existiert, andernfalls 0. Die Funktion heißt in der DOSVersion Exists.
20.2.4
Verzeichniseintrag suchen - FirstDir
FirstDir (Suchmuster: Zeichenkette; Attribute: Zeichenkette): Zeichenkette
- 214 -
Betriebssystemfunktion
Sucht den ersten Verzeichniseintrag, der dem Suchmuster und den Attributen entspricht und liefert einen String mit dem Dateinamen, der Dateigröße und
weiteren Angaben zurück. Das Suchmuster enthält normalerweise Joker-Zeichen wie z.B. "*.*" oder "*.DAT" oder "MAI???.K*". Attribute gibt an, welche Dateien
zusätzlich zu den normalen Dateien gesucht werden sollen und stimmt mit den Attributen im Datei-Manager überein:
Attribute
Attribute
R Nur lesen (Read Only)
A Archiv
S System-Datei
H Versteckt (Hidden)
V Datenträger-Bezeichnung (Volume ID)
D Verzeichnisse (Directory)
Anstelle eines Attribut-Strings kann auch das Zeichen "X" angegeben werden. In diesem Fall enthält der Rückgabestring den kompletten Pfad zur angegebenen
Datei.
Der Rückgabe-String enthält Angabe über die gefundene Datei:
Zeichen
Inhalt
1..12
15..24
Dateiname mit Extension
Dateigröße in Byte
27..36
39..43
46
Datei-Datum
Datei-Zeit
"R" bei Nur-lesen-Dateien,
ansonsten leer
"H" bei versteckten Dateien,
ansonsten leer
47
Zeiche Inhalt
n
47
"S" bei System-Dateien, ansonsten leer
47
"V" bei Datenträger-Bezeichnung,
ansonsten leer
47
"D" bei Verzeichnissen, ansonsten leer
47
"A" bei Archiv-Dateien, ansonsten leer
54..
Verzeichnis-Name
Beispiel für letzte Dateiänderung
"Letzte DB-Aenderung: " + FirstDir("ADRESSEN.DAT","")[27,18]
20.2.5
Nächsten Verzeichniseintrag suchen - NextDir
NextDir: Zeichenkette;
Kann nur nach einem vorangegangenen FirstDir aufgerufen werden. Liefert die Beschreibung des nächsten passenden Verzeichniseintrags.
Beispiel siehe FirstDir.
20.2.6
Freien Speicherplatz auf der Festplatte ermitteln - DiskFree
DiskFree : REAL
liefert den freien Platz auf der Diskette/Festplatte in Bytes. Die Zahl bestimmt das Laufwerk:
Laufwerk
aktuelles
A:
B:
C:
D:
E:
usw.
Laufwerk
- 215 -
Betriebssystemfunktion
Zahl
0
1
2
3
4
5
usw.
Das Ergebnis -1 erhält man, falls das angegebene Laufwerk nicht vorhanden bzw. keine Diskette eingelegt ist.
Achtung: Bei Netzwerklaufwerken erhält man generell den Rückgabewert 0.
Beispiel:
WHILE DiskFree(1) < 10000
cgiwrite("Zu wenig Platz auf der Diskette","Achtung",1)
END
20.2.7
Umgebungsvariable auslesen - GetEnv
GetEnv (Umgebungsvariable: String): String
Liefert den Wert einer Umgebungsvariablen, falls diese definiert ist, andernfalls einen Leerstring.
Beispiele:
Execute(GetEnv("COMSPEC"),"")
GetEnv("WINDIR")
Das erste Beispiel führt die DOS-Shell aus, das zweite liefert das aktuelle Windows-Verzeichnis.
21.
Lesen und Schreiben von Textdateien
Wenn man Textdateien einliest und daraus Werte isolieren möchte, ist dabei die Fähigkeit, mit einem String umzugehen sehr wichtig.
Für das Lesen und Schreiben von Textdateien stehen folgende Befehle zur Verfügung:
Close
EOT
Include
Read
ReadLn
Reset
Rewrite
Tappend
Uses
Write
Textdatei schließen
End of Text
Textdatei einbinden
Aus Textdatei lesen
Read(nFileHandle) liest ein Zeichen aus der Textdatei
Read(nFileHandle,Anzahl) liest Anzahl Zeichen aus der Textdatei
Ganze Zeile aus Textdatei lesen
Textdatei zum Lesen öffnen
Textdatei zum Schreiben öffnen
Datei zum Anhängen öffnen.
Einbinden von anderen Modulen in ein Modul
Ausgabe in Textdatei
- 216 -
Betriebssystemfunktion
WriteLn
21.1
Ausgabe in Textdatei mit Zeilenvorschub
Textdatei zum Lesen öffnen - Reset
Reset(Dateiname: String) : REAL
Öffnet eine Textdatei zum Lesen. Falls die Datei erfolgreich geöffnet werden kann, liefert die Funktion eine Zahl (=Texthandle) über die in der Folge auf die Datei
(Read, ReadLn, Eot, Close) zugegriffen wird. Beispiel siehe Funktion Read
21.2
Textdatei zum Schreiben öffnen - Rewrite
Rewrite(Dateiname: String): Real
Öffnet Textdatei zum Schreiben. Falls die Datei erfolgreich erzeugt werden kann, liefert die Funktion eine Zahl (=Texthandle) über die in der Folge auf die Datei
(Write, WriteLn, Close) zugegriffen wird. Besteht bereits eine Datei mit dem angegeben Namen, so wird diese überschrieben. Soll eine Datei zum
Weiterschreiben geöffnet werden, muß die Funktion TAppend verwendet werden.
21.3
Text an Datei anhängen - TAppend
TAppend(Dateiname: String): Real
Öffnet die Datei zum Anhängen von Daten an die bestehende Datei. Siehe Rewrite
21.4
Texthandle schließen - Close
Close(Zahl)
Die Zahl entspricht einem Texthandle, der mit einer der Funktionen Reset, Rewrite oder TAppend erzeugt wurde. Close schließt die zugehörige Textdatei.
21.5
Zeichen aus einer Textdatei lesen - Read
Read(Zahl1,Zahl2)
Bei Zahl1 handelt es sich um einen über die Funktion Reset festgelegten Texthandle. Der optionale zweite Parameter gibt an, wieviele Zeichen gelesen werden.
Ist er nicht vorhanden, so wird nur ein Zeichen gelesen. Der Maximalwert für Zahl2 ist 255. Das Ergebnis der Funktion ist die gelesene Zeichenkette. Wird über
das Ende der Textdatei hinaus gelesen, so werden entsprechend weniger Zeichen gelesen und die Funktion Eot liefert den Wert 1.
21.6
Zeile aus einer Textdatei lesen - ReadLn
ReadLn(Zahl)
Bei Zahl handelt es sich um einen über die Funktion Reset festgelegten Texthandle. Aus der zugehörigen Textdatei wird eine komplette Zeile bis zum nächsten
CR/LF gelesen. Diese Zeile bildet das Ergebnis der Funktion. CR/LF (=Zeilenvorschub) wird überlesen. Es werden maximal 255 Zeichen der eingelesenen Zeile
als Funktionswert geliefert. Beispiel siehe Funktion StrSort.
21.7
Text in Textdatei schreiben - Write
Write (FileHandle,Zeichenkette)
Schreibt Text in eine Textdatei ohne einen Zeilenabschluß CHR(13)+CHR(10) zu schreiben
- 217 -
Betriebssystemfunktion
21.8
Zeile in Textdatei schreiben - WriteLn
WriteLn(FileHandle,Zeichenkette)
Bei FileHandle handelt es sich um einen über die Funktion Rewrite oder TAppend festgelegten Texthandle. Die Zeichenkette wird in die zugehörige Textdatei
geschrieben. Das Ergebnis ist die Zeichenkette selbst.
Beispiel
VarDef nFI : REAL
nFI := ReWrite("BERICHT.TXT")
WriteLn( nFI,"Dieser Text steht in der Datei BERICHT.TXT")
Close(nFI)
21.9
Textende erkennen -EOT
Eot(Zahl)
Bei der Zahl handelt es sich um einen Texthandle, der mit der Funktion "Reset" ermittelt wurde. Eot liefert den Wert 1, falls das Dateiende erreicht wurde,
andernfalls 0.
Beispiel: Im einem Datenbankjob wird eine externe Textdatei (EXTERN.TXT) ausgedruckt.
.PROLOGUE
.VARDEF Text : REAL
.IF Text:=Reset("EXTERN.TXT")
.
WHILE NOT Eot(Text)
$(ReadLn(Text))
END
.
DO Close(Text)
.END
21.9.1 Beispiel:Textdatei lesen
Als Beispiel möchten wir aus der WINTDB.INI den Wert Tab-Weite auslesen und in einer Variablen speichern.
- 218 -
Betriebssystemfunktion
PROCEDURE READ_INI-SECTION
Var nFI : REAL
Var cSection
: STRING
Var cRet : STRING
cRet := ""
nFI := RESET("C:\WINDOWS\WINTDB.INI")
WHILE EOT( nFI)=0
cSection := ReadLn( nFI)
IF cSection hat "Tab-Weite"
cRet := cSection[POS("=",cSection)+1,1]
END
END
Close(nFI)
RETURN cRet
ENDPROC
Hier folgt eine genaue Beschreibung, was innerhalb der Procedure geschieht:
PROCEDURE READ_INI-SECTION
leitet die Procedure ein
VarDefnFI
: REAL
VarDefcSection
:STRING
hier werden alle benötigten Variablen deklariert
VarDefcRet
cRet
:=
:STRING
""
die Variable cRet wird mit Leer vorbelegt
nFI:=RESET("C:\WINDOWS\WINTDB.I die Datei WINTDB.INI wird geöffnet. Die
NI")
Nummer des FileHandles wird in der Variablen
nFI gespeichert. Kann die Datei aus irgendeinen
Grund nicht gelesen werden, so hat nFI den Wert
0.
WHILE EOT( nFI)=0
cSection
:=
solange das Dateiende nicht erreicht ist, werden
die Anweisungen zwischen While und End
wiederholt
ReadLn( nFI)
nun wird jede Zeile gelesen und in der Variablen
cSection abgelegt
- 219 -
Betriebssystemfunktion
IF cSection hat "Tab-Weite"
falls die Variable cSection den gesuchten Begriff
Tab-Weite enthält
cRet:=cSection[POS("=",cSection)+1,1] wird der der Variablen cSection das nach dem
"="-Zeichen stehende Zeichen der Variablen cRet
übergeben
END
END
sowohl die While-Schleife, als auch die IFSchleife wird wieder geschlossen
Close(nFI)
das FileHandle wird wieder geschlossen
RETURN cRet
der in cRet gespeicherte Wert wird
zurückgegeben
ENDPROC
die Procedure wird beendet
- 220 -
Betriebssystemfunktion
22.
Streamfunktionen
Bisher wurden Streams (untypisierte Daten) immer im R/W-Modus geöffnet. Das kann manchmal lästig sein, in vielen Fällen ist es aber unnötig. Deshalb gibt es
jetzt einen (optionalen) zusätzlichen Parameter, mit dem der Modus festgelegt wird. Näheres siehe in der Supportdatenbank.
Socket-und Streamfunktionen mit direkter Pufferindizierung
Die Funktionen f_read, f_write, getsock und putsock wurden so erweitert, dass beim Übertragungspuffer nun auch ein Feldindex angegeben werden kann:
VAR buf : CHAR[10000]
f_read(hdl,buf[x],n) : INTEGER
f_read(hdl,buf,n) = f_read(hdl,buf[0],n) // wie bisher
Untypisierte Dateien
f_open(filename : string) : integer
f_size(handle : integer) : integer
f_error : integer
22.1
f_create(filename : string) : integer
Alle Funktionen betreffen externe Dateien.
f_create erzeugt eine neue Datei, f_open öffnet eine bestehende Datei.
Beide Funktionen liefern im Erfolgsfall ein Handle (Integer>0), über den alle weiteren Operationen aufgerufen werden. Wird 0 geliefert, so konnte die Datei
nicht geöffnet werden, und in f_error steht der Fehlercoe des Betriebssystems.
22.2
f_close(handle : integer) : integer
f_close schließt eine Datei. Alle geöffneten Dateien werden am Ende desProgramms automatisch geschlossen.
22.3
f_seek(handle, pos : integer) : integer
f_seek positioniert byteweise in der Datei. Die erste Position ist 0. DieFunktion liefert den Fehlercode des Betriebssystems, also 0, wenn in Ordnung.
- 221 -
Betriebssystemfunktion
22.4
f_read(handle : integer; var array [; nbytes : integer) : integer
f_write(handle : integer; var array [; nbytes : integer) : integer
f_read und f_write erlauben das Lesen und Schreiben in die Datei. Der zweite Parameter muss ein Array zur Datenübertragung sein. Wird kein dritter Parameter
angegeben, so wird das gesamte Feld gelesen bzw. geschrieben. Das Funktionergebnis ist die Anzahl der wirklich übertragenen Bytes.
Beispiel Schreiben und Lesen der in einem TBits-Array gespeicherten
Markierungen:
PROCEDURE SaveMarks(VAR marks : TBITS[])
VAR hdl : INTEGER
IF hdl:=f_create('marks/selection')>0 THEN
f_write(hdl,marks); f_close(hdl)
END
ENDPROC
PROCEDURE RestoreMarks(VAR marks : TBITS[])
VAR hdl : INTEGER
IF hdl:=f_open('marks/selection')>0 THEN
f_read(hdl,marks); f_close(hdl)
END
ENDPROC
22.5
f_pos
Liefert die aktuelle Position in einem Stream:
f_pos(hdl : INTEGER) : INTEGER
VAR hdl : INTEGER = f_open('./test.prg',0)
VAR buf : BYTE[1000]
...
IF hdl>0 THEN
f_read(hdl,buf[0],10)
cgiwriteln(str(f_pos(hdl)) // ergibt 10
END
22.6
f_close()
Schließt einen neuen Stream
- 222 -
Betriebssystemfunktion
22.7
f_error()
Liefert den Fehlercode des Betriebssystems für die letzte Operation
22.8
f_find() Findet String
22.9
f_replace()
Ersetzt target durch replacement
22.10
f_seek()
Positioniert in einem Stream
22.11
f_size()
Ermittelt die Größe eines Stream
22.12 LoadTemplateviaProxy
Diese interne Erweiterung wurde bereits oben bei HTTP_PROXY erläutert.
- 223 -
Betriebssystemfunktion
23.
Systemfunktionen
Funktion
chdir()
chmod()
compile()
copyfile()
delfile()
delident()
dirinfo()
diskfree()
DO
execprog()
firstdir()
flush()
getdir()
getenv()
getident()
getpara()
getsize()
halt()
isfile()
makedir()
nextdir()
privdir()
remdir()
rename()
setpara()
testident()
timeout()
today()
varname()
Beschreibung
Verzeichniswechsel
Dateirechte ändern
Compiliert einen EASY-Quelltext in den PRG-Zwischencode
Legt die Kopie einer Datei an
Löscht eine externe Datei
Löscht einen Eintrag aus einer Konfigurationsdatei
Verzeichniseintrag für eine Datei
Freien Platz auf Speichermedium ermitteln
Kompilert und führt beliebigen EASY-Code zur Laufzeit aus
Liefert den ersten Verzeichniseintrag zu einem Suchmuster
Übertragung sämtlicher gepufferter Daten
ermittelt das aktuelle Verzeichnis
Environment-Variable ermitteln
Holt einen Eintrag aus einer Konfigurationsdatei
Wert eines Laufzeitschalters ermitteln
Liefert die Größe einer Datei
Programm benden
prüft Existenz einer Datei oder eines Verzeichnisses
legt ein neues Verzeichnis an
nächster Verzeichniseintrag bzgl FirstDir
Gibt absoluten Pfad zur tdbengine zurück
Löscht ein (leeres) Verzeichnis
Gibt einer Datei einen neuen Namen
Setzt einen Laufzeitschalter
Prüft, ob ein Eintrag in einer Konfigurationsdatei vorhanden ist
(nur Linux) Bricht die Ausführung einer Prozedur nach abgelaufener Frist ab
Systemdatum
Liefert den Deklarationsnamen einer Variablen
- 224 -
Betriebssystemfunktion
23.1
chdir() – Verzeichniswechsel
Beschreibung
ChDir wechselt in das angegebene Verzeichnis.
Syntax
ChDir(Verzeichnis: String): INTEGER
Parameter
Verzeichnis: Name des Verzeichnisses.
Auch unter Windows können als Verzeichnistrennzeichen normale Slash (/) statt der in diesem Betriebssystem üblichen Backslash (\)
angegeben werden.
Ergebnis
Der Rückgabewert ist 0 wenn der Wechsel erfolgreich war, andernfalls 2.
Beispiel
ChDir('/home/tdbengine/database/adress')
- 225 -
Betriebssystemfunktion
23.2
compile() - Compiliert einen EASY-Quelltext in den PRG-Zwischencode
Beschreibung
Wenn beim Kompilieren ein Fehler auftritt (Rückgabewert ungleich 0), so sind folgende Systemvariablen gesetzt:
TDB_LastError : Fehlercode
TDB_ErrorLine : Zeilennummer im Programm
TDB_ErrorOfs : Spalte in der Programmzeile
TDB_ErrorPos : Zeichenposition im Quelltext
TDB_ErrorMsg : Fehlerhafter Ausdruck
Syntax
COMPILE(mod : STRING [; prg : STRING]) : INTEGER
Parameter
mod : Pfad zum EASY-Quelltext
prg : Pfad zur PRG-Datei
Ergebnis
Fehlercode
0 : erfolgreich kompiliert
Hinweis: (18.07.2013/hg
Mit uses eingebundene Module werden in der Reihenfolge eingebunden.
Das bedeutet, dass bei gleichen Procedurenamen die zuletzt gelesene Procedure definiert wird.
23.3
copyfile() - Legt die Kopie einer Datei an
Beschreibung
Als Quelldateien kommen in Frage:
externe Dateien
Ramtexte ("ramtext:...")
Als Zieldateien kommen in Frage:
externe Dateie
Ramtexte ("ramtext:...")
Standardausgabe ("con")
- 226 -
Betriebssystemfunktion
Syntax
COPYFILE(from, to : STRING) : INTEGER
- 227 -
Betriebssystemfunktion
Parameter
from : Pfad zur Ausgangsdatei
to : Pfad zur Zieldatei
Ergebnis
Fehlercode
0 : erfolgreich kopiert
23.4
delfile() - Löscht eine externe Datei
Beschreibung
Achtung: Die Funktion löscht auch Dateien, die mit dem RO-Attribut ausgestattet sind.
Die Funktion kann nicht auf interne Dateien (Ramtexte) angewendet werden.
Syntax
DELFILE(path : STRING) : INTEGER
Parameter
path : zu löschende Datei
Ergebnis
Fehlercode
0 : erfolgreich gelöscht
23.5
delident() - Löscht einen Eintrag aus einer Konfigurationsdatei
Syntax
DELIDENT(ini, ident : STRING) : STRING
Parameter
ini : Pfad zur Konfigurationsdatei
ident: Name des gesuchten Wertes
- 228 -
Betriebssystemfunktion
23.6
dirinfo() - Verzeichniseintrag für eine Datei
Beschreibung
Die Funktion liefert das selbe Ergebnis wie FirstDir(Path,''), kann aber auch innerhalb einer FirstRec - NextRec - Sequenz eingesetzt werden.
Syntax
DirInfo(Path : STRING) : STRING
Parameter
Path : Pfad zur Datei
Ergebnis
String wie bei FIRSTDIR, NEXTDIR
23.7
diskfree() - Freien Platz auf Speichermedium ermitteln
Beschreibung
Liefert unter Linux im Augenblick bei Restkapazitäten >2 GByte "-1" zurück.
Syntax
DISKFREE(Laufwerk : INTEGER) : INTEGER
Parameter
Laufwerk:
Unter Win32
0 = aktuelles Laufwerk
1 = A: 2 = B: 3 = C: usw.
Unter Linux:
0 = aktuelle Partition (kann mit CHDIR angewählt werden)
Ergebnis
Freier Speicherplatz in Bytes.
- 229 -
Betriebssystemfunktion
23.8
DO
Syntax
DO _s : STRING
23.9
execprog() - Kompilert und führt beliebigen EASY-Code zur Laufzeit aus
Beschreibung
Bei dieser Funktion handelt es sich eigentlich um eine alte TDB-Funktion, die jetzt in der tdbengine reaktiviert wurde. Der Parameter cFilename verweist auf eine
Textdatei, die EASY-Code enthält. Dieser Code wird zunächst kompiliert und dann sofort ausgeführt. Gerade in Verbindung mit Ramtexten ergeben sich sehr
schöne Möglichkeiten: Man setzt sich komplexe Datenbankabfragen in einem Ramtext zusammen und führt diesen dann aus.
Beim Einsatz von ExecProg ist zweierlei zu beachten:
 Innerhalb des auszuführenden Codes kann man zwar auf alle Variablen des aufrufenden Programmes zugreifen, nicht aber auf die Prozeduren und
Funktionen.
 Im Code verwendete Variablen werden durch den Compiler verändert, müssen also im Code explizit auf den aktuellen Wert gesetzt werden.
 Die Initalisierung von variablen direkt nach der Deklaration ist nicht möglich.
(also nicht var A : Integer = Opendb(„address.dat“), sondern in 2 Zeilen schreiben)
Syntax
execProg(cFilename : STRING) : INTEGER
Parameter
cFilename : Pfad zur Quellcode-Datei.
Kann auch ein Ramtext sein ("ramtext:...")
- 230 -
Betriebssystemfunktion
23.10 firstdir() -Liefert den ersten Verzeichniseintrag zu einem Suchmuster
Beschreibung
Ein Verzeichnis-Ifo ist ein String mit folgendem Aufbau:
Zeichen
1 bis 63
Inhalt
Dateiname
64 bis 70
71 bis 82
84 bis 94
96 bis 108
110 bis 127
128 bis 255
Attribute (d = Directory ... )
Dateigrösse
Datum der letzten Änderung
Zeit der letzten Änderung
Zugriffsrechte (dereit nur unter Linux)
Verzeichnis absolut
Die Dateiattribute werden nur unter Win32 ausgewertet. Hier werden zusätzlich zu normalen Dateien gesucht:
'D' : Verzeichnisse
'H' : versteckte Dateien
'S' : Systemdateien
FIRSTDIR-NEXTDIR-Sequenzen können nicht geschachtelt werden (also kein rekursives Durchsuchen eines Verzeichnisbaums). Die Systemressourcen werden
von der tdbengine verwaltet, es muss keine explizite Freigabe erfolgen.
Innerhalb einer FIRSTDIR-NEXTDIR-Sequenz kann jedoch dir Funktion DIRINFO eingesetzt werden.
Syntax
FIRSTDIR(Pattern, Attribute : STRING) : STRING
Parameter
Pattern: Suchmuster
Attribute : (Nur unter Win32)
Ergebnis
Verzeichnis-Info, wenn gefunden
Leerstring sonst
- 231 -
Betriebssystemfunktion
Beispiel:
c:=FirstDir('../def/*.def','')
while c#''
cName:=Exchange(Lower(LTrim(RTrim(c[1,63]))),'.def','')
c:=NextDir
End
23.11
getdir() - ermittelt das aktuelle Verzeichnis
Syntax
GETDIR(drive : INTEGER) : STRING
Parameter
drive : Laufwerk
0 : aktuelles Laufwerk
1 : A:
2 : B:
...
Unter Linux/FreeBSD immer 0
Ergebnis
Aktuelles Verzeichnis in Unix-Schreibweise
23.12 getenv() Kurz Environment-Variable ermitteln
Beschreibung
GetEnv liefert bekanntermaßen den Wert der Environment-Variablen mit dem angegebenen Namen
Es werden maximal 255 Zeichen zurückgeliefert. Um dieser Beschränkung zu entgehen, wird nach jedem GETENV ein Ramtext "ramtext:environment"
eingerichtet, der den kompletten Wert der gesuchten Variablen enthält.
Mit einer Spezialform der Funktion GetEnv kann man auch Variablen für das interne Environment der tdbengine setzen:
Neu ist, dass nach jedem Aufruf ein Ramtext mit dem Namen "ramtext:environment" zur Verfügung steht, der ebenfalls die Environmentvariable enthält. Damit
kann auch auf Einvironment-Variablen zugriffen werden, deren Inhalt 255 Zeichen übersteigt.
- 232 -
Betriebssystemfunktion
CgiCloseBuffer
CgiWrite("<pre>")
GetEnv("HTTP_ACCEPT")
CopyFile("ramtext:environment","con")
CgiWriteLn('</pre>')
GetEnv('set:Varibale=Wert')
Syntax
GETENV(name : STRING) : STRING
Parameter
name : beliebiger STRING
Ergebnis
Environment-Variable mit dem angegeben Namen.
Beispiel:
GetEnv('set:MEIN_NAME=Hans Mustermann')
In der Folge liefert GetEnv('MEIN_NAME') den String 'Hans Mustermann'. Diese Variable dann solange gültig, bis sie entweder umdefiniert oder aber die
tdbengine beendet wird.
Vorbelegungen:
TDB_VERSION
TDB_OS
set:HTTP_PROXY
set:TDB_SUBST
TDB_LIBPATH
TDB_SEMA
TDB_SEMADIR
- 233 -
Betriebssystemfunktion
23.13 getident() – Holt einen Eintrag aus einer Konfigurationsdatei
Beschreibung
Zu den speziellen Textdateien wollen wir Konfigurationsdateien und Templates zählen. Die tdbengine unterstützt Konfigurationsdateien im folgenden Stil:
[Gruppe 1]
Eintrag1=...
Eintrag2=...
...
[Gruppe 2]
Eintrag1=...
Eintrag2=...
...
Jeder Eintrag kann aus bis zu 255 Zeichen bestehen. Zur Bearbeitung von derartigen Dateien stellt EASY zwei Funktionen zur Verfügung: SETIDENT und
GETIDENT:
SETIDENT(Konfigurationsdatei,Eintrag,Wert) schreibt Eintrag=Wert in Konfigurationsdatei
GETIDENT(Konfigurationsdatei,Eintrag) liefert den zum Eintrag gehörenden Wert Einträge werden dabei in Form »Gruppe.Eintrag«
angegeben.
Syntax
GETIDENT(ini, ident : STRING) : STRING
Parameter
ni : Pfad zur Konfigurationsdatei
ident: Name des gesuchten Wertes
Ergebnis
Der gesuchte Wert aus der Konfigurationsdatei
- 234 -
Betriebssystemfunktion
Beispiel:
PROCEDURE Main
VAR ini : STRING
ini:='test.ini'
cgiwriteln('content-type: text/html')
cgiwriteln('')
SETIDENT(ini,'Administrator.Name','Hans Huber')
SETIDENT(ini,'Administrator.Passwort','geheim')
SETIDENT(ini,'Datenbank.Adressen','database/adressen.dat')
cgiclosebuffer
copyfile(ini,'con')
ENDPROC
Dieses kleine Programm legt die Datei »test.ini« mit folgendem Inhalt an:
[Administrator]
Name=Hans Huber
Passwort=geheim
[Datenbank]
Adressen=database/adressen.dat
Anmerkung: Die erzeugte Datei wird dann auch noch auf dem Bildschirm ausgegeben. Wichtig ist hier der Einsatz von »CGICLOSEBUFFER«, damit der interne
Puffer vor dem Kopiervorgang ausgegeben wird. Mit GETIDENT können die einzelnen Einträge ausgelesen werden.
Beispiele:
GETIDENT('test.ini','Administrator.Name) : 'Hans Huber'
GETIDENT('test.ini',Datenbank.Adressen) : 'database/adressen.dat'
Hinweis: Beim ersten Zugriff auf eine Konfigurationsdatei wird diese komplett in den Arbeitsspeicher des Computers gelesen und über eine Baumstruktur dem
Programm zur Verfügung gestellt. Deshalb ist der Zugriff extrem schnell. Konfigurationsdateien ersetzen in vielen Fällen Datenbanken!
HINWEIS: Wenn Sie GetIdent("","") ausführen, so werden alle im Speicher befindlichen Informationen gelöscht. Der nächste Zugriff auf eine Konfigurationsdatei
liest diese neu ein. Dies ist u.a. im Zusammenhang mit Child-Prozessen der SERVER()-Funktion von Bedeutung.
- 235 -
Betriebssystemfunktion
23.14 getpara() Kurz Wert eines Laufzeitschalters ermitteln
Beschreibung
Über Laufzeitschalter wird das Verhalten der tdbengine gesteuert. Mit Ausnahme von AK werden alle Schalter erst zur Laufzeit aktiviert und gelten grundsätzlich
global.
Setzen und Auslesen
Zum Setzen eines Schalters bestehen zwei Möglichkeiten:
* Punktbefehl (veraltet)
* Funktion SetPara
Ein Punktbefehl ist eine Programmzeile, die mit einem Punkt beginnt (wobei führende Leerzeichen ignoriert werden). Nach diesem Punkt werden die einzelnen
Schalter mit dem jeweiligen Wert hintereinander - und jeweils durch Komma getrennt - geschrieben:
.ec 1, nb 0
Da in der nächsten Hauptversion (6.3.x) die Zeilenstruktur in EASY-Programmen aufgehoben wird, sollte von dieser Art der Schaltersetzung nicht mehr
Gebrauch gemacht werden.
An dessen Stelle tritt die Funktion SetPara :
SetPara(Schalter : STRING) : INTEGER
Hier werden die Schalter als String übergeben (ohne führenden Punkt) - ebenfalls durch Komma getrennt:
SetPara('ec 1, nb 0')
Der Rückgabewert der Funktion ist immer 0, bei einem illegalen Befehl wird ein Laufzeitfehler ausgelöst.
Der Wert eines Schalters kann mit der Funktion GetParam abgefragt werden.
GetPara(Schalter : STRING) : INTEGER
Syntax
GETPARA(para : STRING) : INTEGER
Parameter
para : Laufzeitschalter als String
'ec' : Fehlerbehandlung
- 236 -
Betriebssystemfunktion
'nv' : Auswertung mit VAL
'nb' : Abbruchtest
'em' : Volltext-Modus
Ergebnis
aktuell gesetzter Wert des jeweiligen Schalters
Beispiel:
GetPara('ec') - 0|1 (je nachdem)
23.15 getsize() - Liefert die Größe einer Datei
Beschreibung
GETSIZE kann nur auf Dateien angewandt werden, nicht auf Sockets
Syntax
GETSIZE(path : STRING) : INTEGER
Parameter
path : Pfad zu einer Datei (auch Ramtext)
Ergebnis
Größe der Datei in Bytes.
23.16 halt() - Programm benden
Beschreibung
HALT hält die Ausführung des Programms an. Die tdbengine übergibt die Kontrolle wieder an das aufrufende Programm.
Syntax
HALT
- 237 -
Betriebssystemfunktion
23.17 isfile() - prüft Existenz einer Datei oder eines Verzeichnisses
Beschreibung
Der Funktionswert wird durch eine Verzeichnisanfrage ermittelt. Aus diesem Grund sagt es nicht darüber aus, ob die Datei gelesen oder geschrieben werden
darf.
Syntax
ISFILE(path : STRING) : INTEGER
Parameter
path: Pfad zu Datei/Verzeichnis
Ergebnis
0 : Datei existiert nicht
1 : Datei exisiert
23.18 makedir() - legt ein neues Verzeichnis an
Beschreibung MAKEDIR erzeugt bei der Ausführung keinen Laufzeitfehler. Aus diesem Grund ist das Funktionsergebnis unbedingt zu prüfen, damit Datein nicht
ungewollt in unerwünschten Verzeichnissen landen.
Syntax
MAKEDIR(dir : STRING) : INTEGER
Parameter
dir : neues Verzeichnis
Ergebnis
0 : erfolgreich angelegt
sonst: Fehlercode des Betriebssystems
- 238 -
Betriebssystemfunktion
23.19 nextdir() - nächster Verzeichniseintrag bzgl FirstDir
Beschreibung
Siehe FIRSTDIR
Syntax
NEXTDIR
Ergebnis
Verzeichniseintrag wie bei FIRSTDIR
23.20
Funktion privdir() - Gibt absoluten Pfad zur tdbengine zurück
Syntax
PRIVDIR : STRING
Ergebnis
Pfad
- 239 -
Betriebssystemfunktion
23.21 remdir() - Löscht ein (leeres) Verzeichnis
Beschreibung
Folgende Voraussetzungen müssen erfüllt sein, damit ein Verzeichnis erfolgreich gelöscht werden kann:
Das Verzeichnis muss leer sein, darf also keine einzige Datei enthalten.
Das Programm muss das Recht zum Löschen des Verzeichnisses haben.
Syntax
REMDIR(path : STRING) : INTEGER
Parameter
path : zu löschendes Verzeichnis
Ergebnis
Fehlercode des Betriebssystems,
0 = Verzeichnis wurde gelöscht
23.22 rename() -Gibt einer Datei einen neuen Namen
Beschreibung
Wenn eine Datei nicht umbenannt werden konnte, können eine Reihe von Gründen dafür vorliegen:
Die Datei existiert nicht (mehr)
sie ist (eventuell von einem anderen Anwender) geöffnet
es fehlen die nötigen Ausführungsrechte
der neue Name enthält illegale Zeichen
...
Syntax
RENAME(oldname,newname : STRING) : INTEGER
Parameter
oldname : Pfad zur umzubenennenden Datei
newname : neuer Name
Ergebnis
1 = Datei wurde umbenannt
Fehlercode des Betriebssystems
- 240 -
Betriebssystemfunktion
- 241 -
Betriebssystemfunktion
23.23 setpara() - Setzt einen Laufzeitschalter
Beschreibung
Folgende Schalter (Paradigmen) sind verfügbar:
ec 0|1 Fehlerbehandlung (bei einem Fehler erfolgt auch kein eintrag ins error.log)
nb 0|1 Abbruchsbehandlung
nv 0|1 String-Evaluierung (VAL)
Hinweis:ec 1=Fehlerbehandlung durch PRG / ec 0=Bei Fehler Abbruch des PRG
Syntax
SETPARA(para : STRING) : INTEGER
Parameter
para: Laufzeitschalter als STRING
Ergebnis
Immer 0
- 242 -
Betriebssystemfunktion
23.24
testident() - Prüft, ob ein Eintrag in einer Konfigurationsdatei vorhanden ist
Syntax
TESTIDENT(ini, ident : STRING) : INTEGER
Parameter
ini : Pfad zur Konfigurationsdatei
ident: Name des gesuchten Wertes
Ergebnis
JA
NEIN
23.25 timeout() - (nur Linux) Bricht die Ausführung einer Prozedur nach abgelaufener Frist ab
Beschreibung
Diese Funktion ruft die Linux-Funktion Alarm mit dem übergeben Argument auf.
Der Linux-Kernel sendet dann nach Ablauf der angebenen Zeit (in Sekunden) ein Signal (SIGALRM) an den aufrufenden Prozess.
Die tdbengine verarbeitet dieses Signal, indem sie diejenige Prozedur, die zum Zeitpunkt des Signals gerade abgearbeitet wird, mit RETURN 0 (bzw. RETURN ''
bei einer STRING-Funktion) verlässt.
Wird vorher, (also bevor das Signal gesendet wird) die Funktion TimeOut() mit dem Parameter 0 aufgerufen, so wird kein Signal gesendet.
Mit diesem Mechanismus kann man sich recht einfach Timeouts basteln.
Syntax
Timeout(seconds : INTEGER) : INTEGER
Parameter
seconds : Anzahl der Sekunden bis zum Abbruch oder 0 zum Löschen eines zuvor gesetzten Timeouts
Beispiel 1: Timeout + ReadLn von Konsole
Konsoleneingabe mit 10 Sekunden Timeout zur Eingabe einer ID
- 243 -
Betriebssystemfunktion
PROCEDURE Input : INTEGER
VAR id : STRING
Timeout(10)
writeln(0,'Your account id: ')
id:=readln(0)
TimeOut(0)
RETURN 1
ENDPROC
PROCEDURE Main
cgiclosebuffer
IF Input THEN
writeln(0,'Your input will be processed now...')
ELSE
writeln(0,'Cancel forced by timeout')
END
ENDPROC
23.26 today() - Kurz Systemdatum
Beschreibung
TODAY liefert die Anzahl der Tage seit dem (fiktiven) 31.12.-1
Mit DATESTR(TODAY) erhalten Sie das Systemdatum als Zeichenkette.
Syntax
TODAY
Ergebnis
aktuelles Systemdatum
23.27
varname() - Liefert den Deklarationsnamen einer Variablen
Syntax
VARNAME(VAR x) : STRING
Paramete
x : beliebige EASY-Variable
- 244 -
Betriebssystemfunktion
Ergebnis
Name der Variablen
Beispiel:
VAR adresse : STRING
VAR gehalt : REAL
...
VarName(adresse) : 'adresse'
VarName(gehalt) :'gehalt'
Bei Wertparametern wird immer der Parametername zurückgeliefert
PROCEDURE ParameterTest(x : REAL) : STRING
RETURN VarName(x)
ENDPROC
ParameterTest(gehalt) : 'x'
Bei Referenzparamtern wird hingegen der Name der Variablen im Aufruf zurückgeliefert:
PROCEDURE ParameterTest(VAR x : REAL) : STRING
RETURN VarName(x)
ENDPROC
ParameterTest(gehalt) : 'gehalt'
- 245 -
Betriebssystemfunktion
24.
Ramtexte – das Arbeiten mit virtuellen Textdateien
24.1
Ramtext:~plain – 10.06.2013
Liefert die POST-Daten vom Browser ohne Konvertierung.
Für get gibt es auch einen ramtext.
24.2
Allgemeine Information zu Ramtexten
Ein Ramtext ist eine Textdatei, die ausschließlich im Arbeitsspeicher angelegt wird und deshalb einen sehr schnellen Zuriff bietet. Die tdbengine erkennt einen
Ramtext am Dateinamen. Beginnt dieser mit "ramtext:..." so handelt es sich um einen selbstdefinierten Ramtext. Zusätzlich steht ein spezieller Ramtext mit dem
Namen "ramtext" zur Verfügung.
Nur auf diesen Ramtext ist sind folgende Funktionen anwendbar:
loadtemplate(...)
lädt ein Template
subst(...)
führt eine Ersetzung durch
cgiwritetemplate
gibt ein Tempate aus
SetIdent trägt einen Wert in eine Ini-Struktur ein
GetIdent
liest einen Wert aus einer Ini-Struktur
Für alle Ramtexte gibt es neben den oben angesprochenen noch:
OemToAnsi(ramtext) Zeichenkonvertierung von ASCII nach ANSI AnsiToOem(ramtext) Zeichenkonvertierung von ANSI nach ASCII
Daneben kann in Ramtext immer auch dann eingesetzt werden, wenn eine Textdatei erlaubt ist.
Beispiele:
copyfile('/home/tdbengine/templates/index.html','ramtext:index.html') So können bei bestehender Internetverbindung Templates via http-Protokoll von entfernten Rechnern geholt werden. Die tdbengine verwendet dabei das
Protokoll HTTP/1.1, das auch mit virtuellen Hosts (und chunked documents) zurechtkommt.
readmemo(datei,"Bemerkung","ramtext:memo") copymemo(datei,"Bemerkung","ramtext:memo",0) - 246 -
Betriebssystemfunktion
texthandle:=reset("ramtext:memo") texthandle:=rewrite("ramtext:memo")
texthandle:=tappend("ramtext:memo")
Folgende Funktionen sind nicht für Ramtexte verwendbar:
defile(..) rename(...) Hier ein kleines Beispiel, das ein Memo in einen Ramtext einliest, und diesen dann im HTML-Format ausgibt, wobei die harten Zeilenumbrüche mit dem <p>-Tag
ergänzt werden:
VARDEF handle : REAL VARDEF c : STRING copymemo(datei,"Bemerkung","ramtext:memo") IF handle:=reset("ramtext:memo")
WHILE NOT EOT(handle)
c:=read(handle)
cgiwrite(tohtml(c))
IF c=chr(10) .. = LF
cgiwrite('<p>')
END
END
close(handle)
END
- 247 -
Betriebssystemfunktion
24.3
Ramtext(Filename : STRING; InitSize : INTEGER) - Initialisierung eines Ramtext
Ramtexte wachsen inkrementell mit dem Bedarf. Das heisst, sie werden anfangs recht klein dimensioniert und stetig erweitert, wenn mittels WRITE oder SUBST
mehr Platz benötigt wird.
Dieses Vorgehen schont die Systemressourcen, da der Speicher nur bei Bedarf angefordert wird.
Normalerweise werden Ramtexte mit 16 KByte initialisiert (ausser wenn sie bei der Erzeugung schon mehr Platz benötigen). Sie wachsen dann bei Bedarf
ebenfalls in 16 KByte-Schritten.
Mit dieser Funktion kann das geändert werden. Die Initialisierungsgröße kann z.B. gleich wesentlich größer eingestellt werden,damit die automatischen
Inkremenierungsschritte unter bleiben,die bei sehr großen Ramtexten zu einem enormen Bedarf an Arbeitsspeicher führen können.
24.4
Für Ramtexte gibt es einen Satz an Funktionen – 24.11.2011
24.4.1
ramtext_find(ramtext,string[,startpos]) : findpos
Damit kann man eine Zeichenkette in einem Ramtext suchen. Der optionale Parameter startpos gibt den Offset im Ramtext an, ab dem gesucht wird. Der
Rückgabewert entspricht der Position des ersten Zeichens der Übereinstimmung (immer vom Anfang des Ramtexts aus gesehen) bzw. 0, wenn der string nicht
gefunden wird. Die Funktion entspricht in etwa der Funktion pos bei normalen Zeichenketten. 0 ist auch das Ergebnis, wenn der Startwert hinter der aktuellen
Dateigröße des Ramtext liegt.
Beispiel:
While RAMText_Find("ramtext",Chr(10)) Do Subst(Chr(10),"<br>") End
Diese Zeile ersetzt alle Zeilenumbrüche in ramtext gegen ein <br>.
Damit kann man einen normalen Text in html vernünftig lesbar ausgeben.
24.4.2
ramtext_insert(ramtext,startpos,string) : moved_chars
Diese Funktion fügt eine Zeichenkette an der Position startpos ein. Der Rückgabe ist eher von akademischem Interesse, denn es handelt sich dabei um die
Anzahl der verschobenen Zeichen, also Dateigröße - startpos.
Parameter
ramtext : Ramtextname der Form "ramtext:..."
target : Suchstring (ohne Joker!)
- 248 -
Betriebssystemfunktion
startpos: Position, ab der gesucht wird (Vorgabe=1)
mode : 0 (Vorgabe) Groß-/Kleinschreibung beachten
1 Groß/-Kleinschrebing ignorieren
startpos: Position des Zeichens, an der die Zeichenkette eingefügt wird
target : die einzufügende Zeichenkette
Ergebnis: Anzahl der durch die Einfügung verschobenen Zeichen
Der Wert 0 wird zurückgegeben, wenn der String nicht eingefügt werden konnte, weilals startpos der Wert 0 übergeben wurde oder startpos größer als die
aktuelle Dateigröße+1 war
Für startpos sind demnach nur die Werte von 1 (Einfügen ganz am Anfang) bis getsize(ramtext)+1 (Einfügen ganz am Ende) gültig.
24.4.3
ramtext_delete(ramtext : STRING; startpos, anzahl : INTEGER) : INTEGER
Löscht aus einem Ramtext ab der Startposition eine vor gegebene Menge von Zeichen. Diese Zeichenmenge wird aber vor dem Löschen in den Ramtext
"ramtext:~clip"kopiert (also wie bei der berühmten Zwischenablage).
Für startpos sind Werte zwischen 1 und getsize(ramtext) erlaubt.
Beispiel:
Var n: Real
// Zeile in die Zwischenablage holen
n := RAMText_Find('ramtext:rtf','#Start#',0)
RAMText_Delete('ramtext:rtf',n,13)
RAMText_Insert('ramtext:rtf',n,'#Start#')
Praxistip:
Wird innerhalb des Programmabschnittes auch mit SetIdent gearbeitet, kann es vorkommen, dass der Inhalt der Setident-Zuweisung im ramtext:~clip steht und
bei einem copyfile(„ramtext:~clip“,“con“) ausgegeben wird. Hier gibt es also einen Konflikt da setident ebenfalls ramtext_clip benutzt
24.4.4
ramtext_copy(ramtext:STRING;startpos,anzahl:INTEGER):INTEGER
Sie macht das Gleiche wie "ramtext:_delete", nur dass die Zeichen nicht gelöscht,sondern nur nach"ramtext:~clip"kopiert werden.
24.4.5
ramtext_paste(ramtext:STRING;startpos:INETEGER):INTEGER
Hier wird der Inhalt von"ramtext:~clip"in den Ramtext ab der Startposition "startpos" komplett eingefügt.
- 249 -
Betriebssystemfunktion
24.4.6
ramtext_part(ramtext,startpos,count) : string
Mit dieser Funktion kann man Zeichenketten aus einem Ramtext extrahieren. Es werden count Zeichen ab startpos zurückgeliefert. Ist startpos=0 oder zeigt
startpos über das Dateiende hinaus, so wird ein Leerstring zurückgeliefert. Für cou nt sind Werte von 0 bis 255 zulässig.
24.4.7
ramtext:~clip
Die tdbengine stellt neben „ramtext“ einen weiteren System-Ramtext unter dem Namen "ramtext:~clip" zur Verfügung. Dieser ist anfangs leer, kann aber mit allen
bisher bekannten Mitteln gelesen und geschrieben werden.
24.4.8
ramtext_name(hdl : INTEGER)
Liefert den Ramtext-Namen des Handles, der mit reset, rewrite oder tappend geöffnet wurde.
24.4.9
ramtext_subst(ramtext : STRING; target : STRING; substitution [; mode] : INTEGER) : INTEGER
Ersetzt in einem Ramtext eine Zeichenkette durch eine andere Syntax.
target : beliebige Zeichenkette
substitution:
Hier gibt es folgende Möglichkeiten:
String-Ausdruck
dabei gibt es wiederum folgendene Sonderfälle:
"extern:Pfad" target wird durch externe Datei ersetzt
"ramtext:..." target wird durch entsprechenden Ramtext ersetzt
ansonsten wird target durch den String-Ausdruck ersetzt
Feldzugriff, dieser besteht wiederum aus
Tabellenhandle, Feldnummer oder
Tabellenhandle, Feldbezeichner
Mode :
0 (Vorgabe) einfache Ersetzung
1 HTML-Ersetzung (nur Sonderzeichen)
2 ANSI-Ersetzung
4 Zeilenumbrüche werden mit <br>ergänzt
8 ExtNote wird nach der ersten Verwendung gelöscht
16 Externer Text ist im ASCII-Format
- 250 -
Betriebssystemfunktion
32 Nur Body-Teil wird gelesen (nur in Verbindung mit externen HTML-Texten)
Ergebnis:
0 = Target wurde nicht gefunden
1 = Target wurde gefunden
Beschreibung SUBST ersetzt grundsätzlich nur das erste Vorkommen des targets.
Alle targets werden so ersetzt:
WHILE SUBST... END
Warnung:
Die folgende Anweisung verliert sich in einer Endlosschleife, wenn der Ramtext leer ist.
Sicherheitshalber sollte man daher mit getSize prüfen, ob der Ramtext auch einen Inhalt hat.
While RAMText_Subst(“ramtext:a”,",",Chr(10)) do end;
24.4.10 subst(target : STRING; (replacement : STRING | db : INTEGER; field)[; mode : INTEGER]) : INTEGER
Parameter
target : beliebiger STRING
replacement : beliegiger STRING
db : Tabellenhandle von OpenDB
field : Feldnummer oder Feldbezeichner (als String)
mode : INTEGER (0 = Vorgabe)
Ergebnis
0 : target wurde nicht gefunden
# 0 : Ersetzung wurde durchgeführt
Beschreibung
Die Funktion SUBST gehört zur Gattung der Universalfunktionen.
Sie ist mehrfach überladen, das bedeutet, sie kann mit recht unterschiedlichen Parametern aufgerufen werden.
Hier die einfachste Form:
SUBST(target,a_string)
ersetzt das erste Vorkommen von target durch string
- 251 -
Betriebssystemfunktion
Wir haben ein Template, in das wir das aktuelle Datum und die aktuelle Uhrzeit (Serverzeit) einfügen wollen.
Im Template steht dazu folgender Text:
Datum: #datum#
Zeit: #zeit#
Hinweis:
(die Einfassung von Targets mit dem '#'-Zeichen hat sich als recht vorteilhaft herausgestellt, weil hierbei die Verwechslungsgefahr am geringsten ist)
Das Template sei unter templates/meine_seite.html« gespeichert. Das folgende kleine Programm löst dann unsere Aufgabe:
PROCEDURE Main
cgiwriteln('content-type: text/html')
cgiwriteln('')
IF LoadTemplate('templates/meine_seite.html')=0 THEN
subst('#datum#',datestr(today))
subst('#zeit#',timestr(now,0))
CGIWriteTemplate
ELSE
cgiwriteln('template wurde nicht gefunden')
END
ENDPROC
Wenn der einzufügende String mit »extern:« beginnt, wird nicht etwa dieser String eingefügt, sondern der Rest des Strings wird als Pfad zu einer Textdatei
interpretiert und diese ersetzt dann das Target. Entsprechend verweist ein String, der mit »ramtext:« beginnt, auf eine interne Textdatei.
Gerade, wenn das Target durch ganze Textdateien ersetzt werden sollen, wird die Art der Ersetzung wichtig: Was passiert mit Zeilenumbrüchen? In welchem
Zeichensatz soll die Ersetzung durchgeführt werden? Das kann mit einem weiteren Parameter festgelegt werden, dem »Modus«.
SUBST(target,a_string,modus)
Modus
0 : (Vorgabe) -> Ersetzung ohne weitere Bearbeitung
1 : Alle Zeichen werden nach HTML konvertiert
2 -: Alle Zeichen werden nach ANSI konvertiert
4 -: Harte Zeilenumbrüche werden durch <br> ersetzt
16 -: Der externe Text liegt im ASCII-Format vor (statt ANSI)
32 -: Es wird nur der BODY-Teil einer externen HTML-Seite gelesen
- 252 -
Betriebssystemfunktion
128 -: In diesem Fall wird die Groß-/Kleinschreibung beim Target (der zu ersetzenden Zeichenfolge) ignoriert. Man sollte diesen Modus jedoch nur wählen, wenn
er wirklich erforderlich ist, da die zusätzlich benötigte Rechenleistung doch enorm ist.
Diese Modi können auch noch addiert werden, um das gewünschte Ziel zu erreichen. So bedeutet beispieweise der Modus 5 (=1+4), dass bei der Ersetzung eine
Konvertierung nach HTML stattfindet und die harten Zeilenumbrüche durch <br> ersetzt werden.
An Stelle des Ersetzungsstrings kann auch ein Kombination aus Tabellenhandle und Tabellenfeld stehen. Dann wird der Inhalt des angegebenen Feldes
eingefügt.
Der folgende Code ersetzt alle Datenbankfelder im Template.
VAR i, db : INTEGER;
db:=OpenDB(...)
nloop(i,maxlabel(db)-1,subst('#'+label(db,i+1)+'#',db,i+1,5))
24.4.11
Getsize(s:String):Integer
Über getsize kann man sich die physikalische Größe eines Ramtextes ausgeben lassen.
Dies ist z.B. interessant, ob zu prüfen, ob der ramtext eventuell leer ist.
- 253 -
Betriebssystemfunktion
24.5
24.5.1
PraxisBeispiel: Verwendung von Ramtexten
Eingabeformular für ein Adressformuar
Die nachfolgende Procedure zeigt den Einsatz von Ramtexten.
Hier geht es darum, innerhalb eines Templates nach Platzhaltern im Stil $TABELLE.Feld; zu suchen und diese durch die entsprechende DOJO-Anweisung zu
erstzen.
Ausgangsbasis ist folgendes Template, welches ein Adresseingabeformular darstellt.
<table>
$ADDRESS.AutoID;
<tr><td>Name</td><td>$ADDRESS.Name;</td></tr>
<tr><td>Vorname</td><td>$ADDRESS.Name2;</td></tr>
<tr><td>Strasse</td><td>$ADDRESS.Street;</td></tr>
<tr><td>LK/PLZ/Ort</td><td>$ADDRESS.CountryCode; $ADDRESS.Postcode; $ADDRESS.City;</td></tr>
<tr><td>Telefon</td><td>$ADDRESS.Phone;</td></tr>
<tr><td>Fax</td><td>$ADDRESS.Fax;</td></tr>
<tr><td>eMail</td><td>$ADDRESS.Email;</td></tr>
<tr><td>Notizen</td><td>$ADDRESS.Notes;</td></tr>
</table>
Die folgende Procedure sucht nun innerhalb des Template nach $TABELLE.Feldname; , entfernt diese Text und fügt stattdessen die zum Feldtyp passende
DOJO-Anweisung ein.
In den TDB-Tabellen, die mit dojo beginnen, sind alle entsprechenden Anweisungen hinterlegt. Nachdem als klar ist, auf welches Feld zugegriffen werden soll ist
auch der Feldtyp und die dazugehörige DOJO-Anweisung bekannt.
Wir konzentrieren uns in dem Beispielerst einmal nur auf die Verwendung der verschiedenen Ramtext-Funktionen:
Procedure layout
/* Baut ein DojoFormular auf
*/
Var id_form : Integer = Val(GP("id"))
Var tabindex : Integer
Wir initialiseren einen String mit „ramtext:e“
Var sRam : String ="ramtext:e"
Var i,i2 : Integer
- 254 -
Betriebssystemfunktion
Var
Var
Var
Var
Var
Var
Var
f,f2 : Integer
s : String
DB
: Integer
DF
: Integer
DT
: Integer
DI
: Integer
T7L : Integer
=
=
=
=
=
OpenDB(sPath+"sysbase/dojobutton.dat","",0,0)
OpenDB(sPath+"sysbase/dojoform.dat","",0,0)
OpenDB(sPath+"sysbase/dojotyp.dat","",0,0)
OpenDB(sPath+"sysbase/dojoinput.dat","",0,0)
OpenDB(sPath+"sysbase/tdb7labels.dat","",0,0)
readAuto(DF,id_form)
Nun wird das Template entweder als Textdatei von der Platte gelesen oder aus dem Memofeld eines Datensatz in dofojorm.dat.
If GetField(DF,"PathToTemplate"),IsFile(GetField(DF,"PathToTemplate")), MemoLen(DF,"Template")=0
CopyFile(GetField(DF,"PathToTemplate"),sRam)
Else
CopyMemo(DF,"Template",sRam)
End
i := 1
Nun wird das erste $-Zeichen im Ramtext gesucht und die Startposition in der Variablen i gemerkt.
While i := RAMText_Find(sRam,"$",i)
Anschließend wird das schließend “;” gesucht. Dabei setzt man bei der Suche an der Startposition i auf, also an der Stelle, an der das $-Zeichen gefunden
wurden. Der Wert wird in i2 gespeichert.
i2 := RAMText_Find(sRam,";",i)
Dann wird die Zeichenkette zwischen i und i2 ausgeschnitten und in der Variablen s gespeichert.
s:=RAMText_Part(sRam,i,i2 -i)
If ReadRec(T7L,FindRec(T7L,LTrim(RTrim(s)),1,1))
If GetRField(T7L,"dojoTyp"),ReadRec(DI,FindRec(DI,GetField(T7L,"dojoTyp"),"$dojoTyp",1))
ReadRec(DT,FindAuto(DT,GetRField(DI,"dojoTyp")))
Die Zeichenkette wird ausgeschnitten.
RAMText_Delete(sRam,i,i2 -i+1)
If GetRField(T7L,"Modus")=2 // hidden
Die passende DOJO-Anweisung wird nach ramtext:clip geschrieben, da man den Inhalt von ramtext:~clip direkt mit ramtext_paste in den ursprünglichen Ramtext
einfügen kann.
f := Rewrite("ramtext:~clip",0)
Write(f,'<input data-dojo-type="dijit.form.TextBox" ')
Write(f,"data-dojo-props='name:")
Write(f,'"'+GetField(T7L,"Feld")+'"')
Write(f,', style:"visibility:hidden;left:-1000px;top:1000px;position:absolute"')
- 255 -
Betriebssystemfunktion
Write(f,"'>")
Close(f)
Hier fügen wir die DOJO-Anweisung in den ursprünglichen Ramtext an der Position ein,an der vorher unser Platzhalter stand:
i2 := RAMText_Paste(sRam,i)
Das gleiche geschieht, wenn es sich bei dem Feld um ein Link-Feld handelt:
elsIf GetRField(T7L,"Typ")=7
// Typ=Link
f := Rewrite("ramtext:~clip",0)
Write(f,'<input data-dojo-type="tdb.form.'+Choice(GetRField(DI,"dojoTyp")+1,"ComboBox",GetField(DT,"Name"))+'" ')
Write(f,"data-dojo-props='name:")
Write(f,'"'+GetField(T7L,"Feld")+'"')
Write(f,', required:'+Choice(Sel(GetRField(DF,"noValidation")=0,GetRField(T7L,"required")),"true","false"))
Write(f,',tabindex:"'+Str(tabindex++)+'"')
Write(f,',newStoreAfterChars:2')
If GetRField(T7L,"FilteringSelectVarStore")=2
Write(f,',queryExpr: "*${0}*"')
Write(f,',highlightMatch:"all"')
Write(f,',autoComplete:false')
End
Write(f,',getNextSelectionUrl: "../program/getdata.prg?act=getFilterOptions&key=${key}"')
Write(f," '/>")
Close(f);
i2 := RAMText_Paste(sRam,i)
Das wird eine Auswahlbox:
ElsIf GetRField(T7L,"Typ")=8
// Typ=Select
CopyMemo(T7L,"Auswahlliste","ramtext:o")
f2 := Reset("ramtext:o",0)
f := Rewrite("ramtext:~clip",0)
Write(f,'<select data-dojo-type="dijit.form.'+Choice(GetRField(DI,"dojoTyp")+1,"ComboBox",GetField(DT,"Name"))+'" ')
Write(f,"data-dojo-props='name:")
Write(f,'"'+GetField(T7L,"Feld")+'"')
Write(f,', required:'+Choice(GetRField(T7L,"required"),"true","false"))
If GetRField(T7L,"styleWidthPT")
Write(f,', style:"width:'+GetField(T7L,"styleWidthPT")+'pt"')
else
Write(f,',
style:"width:'+Choice(GetRField(DI,"styleWidthPT")+1,Str(GetRField(T7L,"maxLength")*3),GetField(DI,"styleWidthPT"))+'pt"')
end
Write(f,',tabindex:"'+Str(tabindex++)+'"')
Write(f,',placeHolder:"'+GetField(T7L,"placeHolder")+'"')
- 256 -
Betriebssystemfunktion
Write(f,"'>")
While !EOT(f2)
WriteLn(f,ReadLn(f2))
End
WriteLn(f,'</select>')
Close(f);
Close(f2)
i2 := RAMText_Paste(sRam,i)
Das wird eine Memofeld:
ElsIf GetRField(T7L,"Typ")=9 // Typ=Memo
f := Rewrite("ramtext:~clip",0)
Write(f,'<input data-dojo-type="dijit.form.SimpleTextarea" data-dojo-props="')
Write(f,"name:'text:"+GetField(T7L,"Feld")+"',type:'text'")
Write(f,'">')
Close(f);
i2 := RAMText_Paste(sRam,i)
Das wird ein Tabellengrid:
ElsIf GetRField(T7L,"Typ")=12 // Typ=GRID
f := Rewrite("ramtext:~clip",0)
Write(f,'<div style="height:200px;background-color:red" id="grid'+GetField(T7L,"AutoID")+'"></div>')
Close(f);
i2 := RAMText_Paste(sRam,i)
und für alle anderen Feldtypen wird eine Standardeingabefeld erstellt:
Else
f := Rewrite("ramtext:~clip",0)
Write(f,'<input data-dojo-type="dijit.form.TextBox" ')
Write(f,"data-dojo-props='name:")
Write(f,'"'+GetField(T7L,"Feld")+'"')
Write(f,', required:'+Choice(GetRField(T7L,"required"),"true","false"))
If GetRField(T7L,"Modus")=3 //disabled
Write(f,', disabled:true')
End
Write(f,', type:"text"')
If GetRField(T7L,"styleWidthPT")
Write(f,', style:"width:'+GetField(T7L,"styleWidthPT")+'pt"')
else
Write(f,',
style:"width:'+Choice(GetRField(DI,"styleWidthPT")+1,Str(GetRField(T7L,"maxLength")*3),GetField(DI,"styleWidthPT"))+'pt"')
end
Write(f,',tabindex:"'+Str(tabindex++)+'"')
- 257 -
Betriebssystemfunktion
Choice(GetRField(T7L,"maxLength")+1,'',Write(f,',maxLength:"'+GetField(T7L,"maxLength")+'"'))
Choice(Sel(GetField(T7L,"placeHolder")),Write(f,',placeHolder:"'+GetField(T7L,"placeHolder")+'"'),'')
Write(f,"'>")
Close(f)
i2 := RAMText_Paste(sRam,i)
End
Und damit man an der richtigen Stelle weitersucht wird die nächste Startposition in der Varibalen i abgelegt.
i := i+i2+1
End
Hier fehlt nur der Teil, welcher das Template vervollständigt und an die Console ausgibt.
Der vollständigen Quellcode findet man in getform.mod in der Procedure Layout.
EndProc
- 258 -
Betriebssystemfunktion
Das Ergebnis ist ein fertiges DOJO-Formular.
Wir sehen, dass alle Platzhalter gegen zum Teil recht ausführliche DOJO-Anweisungen ersetzt wurden.
<form data-dojo-type="dijit.form.Form" data-dojo-props='id:"___id___", name:"___id___",
style:"height:100%;width:100%;overflow:auto;", acceptCharset:"ISO-8859-1"'><table>
<input data-dojo-type="dijit.form.TextBox" data-dojo-props='name:"AutoID", style:"visibility:hidden;left:1000px;top:1000px;position:absolute"'>
<tr><td>Name</td>
<td><input data-dojo-type="dijit.form.TextBox" data-dojo-props='name:"Name", required:true, type:"text",
style:"width:150pt",tabindex:"1",maxLength:"50"'></td></tr>
<tr><td>Vorname</td><td>
<input data-dojo-type="dijit.form.TextBox" data-dojo-props='name:"Name2", required:false, type:"text",
style:"width:150pt",tabindex:"2",maxLength:"50"'></td></tr>
<tr><td>Strasse</td>
<td><input data-dojo-type="dijit.form.TextBox" data-dojo-props='name:"Street", required:false, type:"text",
style:"width:120pt",tabindex:"3",maxLength:"50"'></td></tr>
<tr><td>LK/PLZ/Ort</td>
<td><select data-dojo-type="dijit.form.FilteringSelect" data-dojo-props='name:"Countrycode", required:true,
style:"width:60pt",tabindex:"4",placeHolder:"Bitte Länderkennzeichen auswählen"'><option value="AT">Österreich</option>
<option value="DE">Deutschland</option>
<option value="UK">England</option>
<option value="FR">Frankreich</option>
<option value="IT">Italien</option>
<option value="ES">Spanien</option>
</select>
<input data-dojo-type="dijit.form.TextBox" data-dojo-props='name:"Postcode", required:false, type:"text",
style:"width:60pt",tabindex:"5",maxLength:"12"'><input data-dojo-type="dijit.form.TextBox" data-dojo-props='name:"City",
required:false, type:"text", style:"width:150pt",tabindex:"6",maxLength:"50"'></td></tr>
<tr><td>Telefon</td>
<td><input data-dojo-type="dijit.form.TextBox" data-dojo-props='name:"Phone", required:false, type:"text",
style:"width:120pt",tabindex:"7",maxLength:"40"'></td></tr>
<tr><td>Fax</td><td>
<input data-dojo-type="dijit.form.TextBox" data-dojo-props='name:"Fax", required:false, type:"text",
style:"width:120pt",tabindex:"8",maxLength:"40"'></td></tr>
- 259 -
Betriebssystemfunktion
<tr><td>eMail</td><td><input data-dojo-type="dijit.form.TextBox" data-dojo-props='name:"Email", required:false, type:"text",
style:"width:300pt",tabindex:"9",maxLength:"100"'></td></tr>
<tr><td>Notizen</td><td><input data-dojo-type="dijit.form.SimpleTextarea" data-dojoprops="name:'text:Notes',type:'text'"></td></tr>
</table>
</form>
24.5.2
Praxis dynamisches Ausführen von Quellcode, der in einem Ramtext steht
Wir haben in einem Ramtext Easy-Code geschrieben und führen diesen dann über Execprog aus:
If GetSize("ramtext:extendOptions")
ExecProg("ramtext:extendOptions")
End
- 260 -
Betriebssystemfunktion
25.
Lesen und schreiben in Konfigurationsdateien – 13.11.2011
Die tdbengine unterstützt in besonderem Maße Konfigurationsdateien, die nach folgendem Schema aufgebaut sind:
[Gruppe 1]
Eintrag1=...
Eintrag2=...
...
[Gruppe 2]
Eintrag1=...
Eintrag2=...
...
Jeder Eintrag kann aus bis zu 255 Zeichen bestehen.
Zur Bearbeitung von derartigen Dateien stellt EASY zwei Funktionen zur Verfügung: SetIdent() und GetIdent().
Zur Bearbeitung von derartigen Dateien stellt EASY zwei Funktionen zur Verfügung: SetIdent() und GetIdent().
SETIDENT(Konfigurationsdatei,Eintrag,Wert)
GETIDENT(Konfigurationsdatei,Eintrag)
25.1
schreibt Eintrag=Wert in Konfigurationsdatei
liefert den zum Eintrag gehörenden Wert
Neuen Wert in Konfigurationsdatei setzen - setident
setident(IniFile,Indentifier,Value:STRING):integer)
Beschreibung
Mit dieser Funktion kann in einer bestehenden Konfigurations-Datei ein neuer Wert gesetzt werden, ohne dass hierfür die Datei komplett neu geschreiben
werden muß. Sie bildet demnach das Gegenstück zu GetIdent.
Die Funktion SetIdent erzeugt Zeilenvorschübe je nach Betriebssystem: LF bei Linux und FreeBSD, CRLF bei Win32.
Mit diesen beiden Funktionen werden Textdateien der folgenden Form bearbeitet:
[Gruppe_1]
Bezeichner_1=Wert_1
Bezeichner_2=Wert_2
...
[Gruppe_2]
Bezeichner_1=Wert_1
- 261 -
Betriebssystemfunktion
Bezeichner_2=Wert_2
…
GetIdent(Inifile,'Gruppe_1.Bezeichner_1') liefert Wert_1
SetIdent(Inifile,'Gruppe_1.Bezeichner_1','Neuer Wert') führt zu folgendem Eintrag
[Gruppe_1]
Bezeichner_1=Neuer Wert
Bezeichner_2=Wert_2
...
SetIdent(Inifile,'Gruppe_1.Bezeichner_3','Neuer Wert') führt zu folgendem Eintrag
[Gruppe_1]
Bezeichner_1=Wert_1
Bezeichner_2=Wert_2
Bezeichner_3=Neuer Wert
SetIdent(Inifile,'Neue_Gruppe.Bezeichner_1','Neuer Wert') führt zu folgendem Eintrag
[Gruppe_1]
Bezeichner_1=Wert_1
Bezeichner_2=Wert_2
...
[Gruppe_2]
Bezeichner_1=Wert_1
Bezeichner_2=Wert_2
...
[Neue_Gruppe]
Bezeichner_1=Neuer Wert
Der neu gesetzte Wert ist sofort gültig, ein Aufruf von GetIdent(....) liefert also immer den aktuellsten Wert .
Achtung: SetIdent führt kein File-Locking durch, sondern liest den beim Aufruf aktuellen Stand der Datei.
Hinweis: Konfigurationsdateien sind ein höchst komfortabler und effizienter Weg zur Verwaltung von nicht-relationalen Daten wie Systemdateien etc.
Der Zugriff auf die einzelnen Bezeichner (Idents) erfolgt extrem schnell: Eine Inidatei wird nur beim ersten Aufruf gelesen, dabei werden sämtliche darin
enthaltenen Informationen in einer sehr schnellen (Baum-)Suchstruktur abgelegt.
Beispielprogramm:
- 262 -
Betriebssystemfunktion
Einträge werden dabei in der Form »Gruppe.Eintrag« angegeben.
PROCEDURE Main
VARDEF ini : STRING
ini:='test.ini'
CGIWriteLn('content-type: text/html')
CGIWriteLn('')
SetIdent(ini,'Administrator.Name','Hans Huber')
SetIdent(ini,'Administrator.Passwort','geheim')
SetIdent(ini,'Datenbank.Adressen','database/adressen.dat')
CGICloseBuffer
CGIWrite('<pre>')
CopyFile(ini,'con')
CGIWrite('</pre>')
ENDPROC
Dieses kleine Programm legt die Datei »test.ini« mit folgendem Inhalt an:
[Administrator]
Name=Hans Huber
Passwort=geheim
[Datenbank]
Adressen=database/adressen.dat
Anmerkung: Die erzeugte Datei wird dann auch noch auf dem Bildschirm ausgegeben. Wichtig ist hier der Einsatz von
CGICloseBuffer(), damit der interne Puffer
vor dem Kopiervorgang ausgegeben wird.
25.2
Holt einen Eintrag aus einer Konfigurationsdatei holen – getident()
Beschreibung
Zu den speziellen Textdateien wollen wir Konfigurationsdateien und Templates zählen. Die tdbengine unterstützt Konfigurationsdateien im folgenden Stil:
Syntax
GETIDENT(ini, ident : STRING) : STRING
Parameter
ni : Pfad zur Konfigurationsdatei
ident: Name des gesuchten Wertes
Ergebnis
Der gesuchte Wert aus der Konfigurationsdatei
- 263 -
Betriebssystemfunktion
Beispiele:
GETIDENT('test.ini','Administrator.Name) : 'Hans Huber'
GETIDENT('test.ini',Datenbank.Adressen) : 'database/adressen.dat'
Hinweis: Beim ersten Zugriff auf eine Konfigurationsdatei wird diese komplett in den Arbeitsspeicher des Computers gelesen und über eine Baumstruktur dem
Programm zur Verfügung gestellt. Deshalb ist der Zugriff extrem schnell. Konfigurationsdateien ersetzen in vielen Fällen Datenbanken!
Hinweis: Wenn Sie GetIdent("","") ausführen, so werden alle im Speicher befindlichen Informationen gelöscht. Der nächste Zugriff auf eine Konfigurationsdatei
liest diese neu ein. Dies ist u.a. im Zusammenhang mit Child-Prozessen der SERVER()-Funktion von Bedeutung.
25.3
Bufferung von INI-Dateien
[globals]
buffer_idents=1
Wenn auf mehrere INI-Dateien im Wechsel geschrieben wird, so empfielt es sicht, die Einstellung buffer_idents=1 in der Section [globals] zu verwenden.
Ist diese Einstelleung aktiviert, so werden mehrere INI-Dateien gleichzeitig im Buffer gehalten.
Andernfalls wird die zuletzt aktuelle INI-Datei aus dem Buffer entfernt und stattdessen die nächste INI-Datei geladen.
Dies kann die Performance beeinträchtigen.
- 264 -
Betriebssystemfunktion
- 265 -
Betriebssystemfunktion
26.
Array Funktionen
Funktion
bitand()
bitandnot()
bitnot()
bitor()
bitsum()
bittrunc()
clrarray()
getauto()
getmarksauto()
high()
inarray()
inarraypos()
initarray()
nbits()
putmarksauto()
'SETSESSIONIDENT'
strsort()
Beschreibung
Schnittmenge zweier Bitfelder
Schnitt eines Bitfeldes mit dem Kompliment eines zweiten
Komplement eines Bitarray
Vereinigungsmenge zweier Bitfelder
Gibt die Summe übereinstimmender Bitwerte aus mehreren Arrays
Schneidet ein TBit-Array exakt
Initialisiert eine Array-Variable mit leeren Einträgen
Kopiert den inr-Index in ein Array
Kopiert die Autonummer markierter Datensätze in ein Integerarray
Felddimension ermitteln
prüft, ob Zahl in Zahlenfeld
InArray mit Rückgabe der Position
dynamische Felddimensionierung
Zählt die gesetzten Bits
Sortiert ein- und zweidimensionale STRING-Arrays
- 266 -
Betriebssystemfunktion
26.1
bitand() -Schnittmenge zweier Bitfelder
Beschreibung
M1 und M2 werden als charakteristische Funktionenen zweier (Teil-)Mengen aufgefasst.
Nach der Ausführung der Funktion enthält M1 die charakteristische Funktion des Schnitts beider Mengen.
Wichtig: Beide Arrays sollten gleich dimensioniert sein, da andernfalls das Ergebnis nur im Bereich des kleineren Feldes gilt.
Syntax
BITAND(VAR M1 : TBITS; VAR M2 : TBITS) : INTEGER
Parameter
M1 : TBITS[]
M2 : TBITS[]
Ergebnis
NBITS(M1) nach Operation ( Cardinalität von M1)
26.2
bitandnot() - Schnitt eines Bitfeldes mit dem Kompliment eines zweiten
Beschreibung
M1 und M2 werden als charakteristische Funktionenen zweier (Teil-)Mengen aufgefasst.
Nach der Ausführung der Funktion enthält M1 die charakteristische Funktion des Schnittes der ersten mit dem Kompliment der
zweiten Menge.
Wichtig: Beide Arrays sollten gleich dimensioniert sein, da andernfalls das Ergebnis nur im Bereich des kleineren Feldes gilt.
Syntax
BITANDNOT(VAR M1 : TBITS; VAR M2 : TBITS) : INTEGER
Parameter
M1 : TBITS[]
M2 : TBITS[]
Ergebnis
NBITS(M1) nach Operation ( Cardinalität von M1)
- 267 -
Betriebssystemfunktion
26.3
bitnot() - Komplement eines Bitarray
Beschreibung
M wird als charakteristische Funktione einer (Teil-)Mengen aufgefasst. Nach der Ausführung der Funktion enthält M die charakteristische Funktion des
Komplements dieser Menge (d.h. jedes Bit wird invertiert).
Mit BitNot besteht jetzt die Möglichkeit, ein Bitarray zu invertieren, also aus jeder 0 eine eine 1 und aus jeder 1 ein 0 zu machen. Das Ergebnis der Funktion ist
die Anzahl der gesetzten Bits nach der Operation.
Syntax
BITNOT(VAR M : TBITS[]) : INTEGER
Parameter
M : TBITS[]
Ergebnis
NBITS(M) nach Operation (Cardinalität von M)
Beispiel 1 BitNot + BitTrunc
VAR x : TBITS[]
InitArray(x[3])
BitSum(x)
// 0 <> bits 000[00000]
BitNot(x)
BitSum(x)
// 8 <> bits 111[11111]
BitTrunc(x,3 +1)
BitSum(x)
// 3 <> bits 111[00000]
Beispiel 2 BitNot + BitTrunc 2 Setzt für jeden Datensatz der Tabelle db das entsprechende Bit im Array
var x : TBits[]
InitArray(x[FileSize(db)];
BitNot(x);x[0]:=0; BitTrunc(x,FileSize(db)+1)
- 268 -
Betriebssystemfunktion
26.4
bitor() - Vereinigungsmenge zweier Bitfelder
Beschreibung
M1 und M2 werden als charakteristische Funktionenen zweier (Teil-)Mengen aufgefasst. Nach der Ausführung der Funktion enthält M1 die charakteristische
Funktion der Vereinigung beider Mengen.
Wichtig: Beide Arrays sollten gleich dimensioniert sein, da andernfalls das Ergebnis nur im Bereich des kleineren Feldes gilt.
Syntax
BITOR(VAR M1 : TBITS; VAR M2 : TBITS) : INTEGER
Parameter
M1 : TBITS[]
M2 : TBITS[]
Ergebnis
NBITS(M1) nach Operation (= Cardinalität von M1)
26.5
bitsum() - Gibt die Summe übereinstimmender Bitwerte aus mehreren Arrays
Beschreibung Die Funktion berechnet das INTEGER-Feld result nach folgender Regel
result[i]:=B1[i]+B2[i]+B3[i]...
Syntax
BitSum(VAR result : INTEGER[]; VAR B1, B2, B3 ... : TBITS[])
Parameter
result : Ziel-Feld in Form eines INTEGER-Arrays
B1..Bn : nahezu beliebige Anzahl an TBit-Arrays (max. 32)
Beispiel 1: BitNot + BitTrunc
VAR x : TBITS[]
InitArray(x[3])
BitSum(x)
// = 0 <> bits 000[00000]
BitNot(x)
BitSum(x)
// = 8 <> bits 111[11111]
BitTrunc(x,3 +1)
BitSum(x)
// = 3 <> bits 111[00000]
- 269 -
Betriebssystemfunktion
26.6
Schneidet ein TBit-Array exakt - bittrunc() (21.11.2011)
Beschreibung
Diese Funktion macht insbesondere dann Sinn, wenn man ein TBit-Array mit BitNot() "umkehrt" und die Grösse des Arrays nicht durch 8 teilbar ist.
Letztlich ist ein TBit-Array ein speziell gehandhabtes BYTE-Array.
Wenn Sie also ein TBit-Array der Grösse 3 erzeugen (z.B. InitArray(a[3]) wird dafür trotzdem der Speicher von InitArray(a[8]) (= 1 Byte) belegt.
Bei einem BitNot(a) würden nun nicht wie gewünscht 3 sondern alle 8 Bit negiert. NBits() würde also nicht, wie erwartet 3, sondern 8 zurückliefern.
Mit BitTrunc(a,4) kann das vermieden werden.
Syntax
BitTrunc(VAR aBits : TBITS[]; nSize : INTEGER)
Parameter
aBits : TBit-Array
nSize : Position des 1. "falschen" Bits
Beispiel 1: BitNot + BitTrunc
VAR x : TBITS[]
InitArray(x[3])
BitSum(x)
// = 0 <> bits 000[00000]
BitNot(x)
BitSum(x)
// = 8 <> bits 111[11111]
BitTrunc(x,3 +1)
BitSum(x)
// = 3 <> bits 111[00000]
Beispiel 2: BitNot + BitTrunc 2 Setzt für jeden Datensatz der Tabelle db das entsprechende Bit im Array
var x : TBits[]
InitArray(x[FileSize(db)];
BitNot(x);x[0]:=0; BitTrunc(x,FileSize(db)+1)
- 270 -
Betriebssystemfunktion
26.7
clrarray() - Initialisiert eine Array-Variable mit leeren Einträgen
Beschreibung
Eine sehr schnelle Funktion, um ein bereits bearbeitetes Array wieder in den (leeren) Ausgangszustand zu versetzen.
Diese Funktion wird bei allen Arrays automatisch vor dem ersten Zugriff ausgeführt.
Syntax
CLRARRAY(VAR ArrayVar)
Parameter
ArrayVar : beliebige Array-Variable
26.8
getauto() - Kopiert den inr-Index in ein Array
Beschreibung
Die Funktion schreibt sämtliche Autonummern in das Feld "an", der Feldindex ist die physikalische Satznummer.
Anwendung:
Anzuzeigen sind 100.000 Datensätze seitenweise 20 Einträge pro Seite. Hier muß man als pro Seitenabruf die nächsten 20 Einträge anzeigen.
Da der aktuelle Prozess vom vorherigen Prozess nichts weiss, müßte man jedesmal die Selektion auf die Datenbank erneut ausführen und
anschließend noch innerhalb der Datenbank so weit die Datensätze lesen, bis man auf die anzuzeigenden Einträge stösst.
Dies würde inakzeptable Laufzeiten bewirken und außerdem den Server noch stark belasten.
Aus diesem Grund ist es besser, man speichert sich das Suchergebnis in Form von markierten Datensätzen in einem Array.
Da das Array wiederum nach Beendigung des Prozesses gelöscht sein würde, speichert man sich das Array in einem Blobfeld.
Mit EmbedBlob und CopyBlob kann man Arrays in ein Blobfeld speichern und wieder auslesen.
Wenn man die Datensätze markiert hat kann man die Laufende_Nummer der jeweiligen Einträge über GETAUTO schneller ermitteln, als über einen Zugriff auf
die Tabelle und über das Auslesen des Feldinhaltes über GetField. Dies spielt bei kleinen Datenmengen noch keine
Rolle, bei mehreren 100.000 Einträgen summieren sich aber die Einzelzeiten und aus diesem Grund ist das Auslesen über GetField nicht mehr praktikabel.
Syntax
GetAuto(db : INTEGER; VAR an : INTEGER[];i:Integer) : INTEGER
Parameter db
Tabellenhandle von OpenDB
an : ARRAY[FilsIze(db)] : INTEGER zum Speichern den Autonummern.
- 271 -
Betriebssystemfunktion
i : Integer 0=Index über Laufende_Nummer, >0 ist eine Indexnummer eines Index über ein Linkfeld (Achtung: noch keine Fehlerbehandlung
bei Angabe eines falschen Index!)
Ergebnis
-1: Feld zu klein dimensioniert
sonst FileSize(db), wenn alles okay
Procedure Mkl2Lfd(cSes,cTable,cBlobMkl,cBlobLfd:String;TimeStamp:Integer)
/*
18.11.2003 hg
Speichert ein TBits Array in einem Integer Array
18/11/03hg : erweitert um TimeStamp
*/
Var i,j,nDB : Integer
Var nMax : Integer
Var aTBits : TBits[]
Var aGetAuto : Integer[]
Var aLfd : Integer[]
nDB := OpenDB("../database/"+cTable+".dat","",0,0)
nMax := FileSize(nDB)
InitArray(aTBits[nMax])
InitArray(aGetAuto[nMax])
GetAuto(nDB,aGetAuto)
CloseAllDB
Blob2TBitArray(cSes,cTable,cBlobMkl,aTBits)
InitArray(aLfd[NBits(aTBits)-1] )
// Ermittlung der markierten Einträge und Übertrag auf .lfd-Datei
If NBits(aTBits)
i:=-1
Repeat
If aTBits[j]=1
aLfd[i++] := aGetAuto[j]
End
Until j++>nMax
End
TBitArray2Blob(cSes,cTable,cBlobMkl,aTBits,TimeStamp)
IntegerArray2Blob(cSes,cTable,cBlobLfd,aLfd,TimeStamp)
EndProc
- 272 -
Betriebssystemfunktion
26.9
getmarksauto() - Kopiert die Autonummer markierter Datensätze in ein Integerarray
Beschreibung
Funktioniert wie GetMarks, nur dass im Array "Autonummern" nicht die physikalischen Satznummern gespeichert werden, sondern eben die Autonummern. Das
Feld sollte mindestens mit nMarks(db) dimensioniert werden.
Die Fehlerbehandlung ist noch nicht perfekt. So prüft die tdbengine derzeit noch nicht, ob überhaut Autonummern vorhanden sind oder ein.inr existiert.
Syntax
GetMarksAuto(db : INTEGER; VAR an : INTEGER[][; index : INTEGER/STRING]) : INTEGER
Parameter
db : Tabellenhandle von OpenDB
an : ARRAY[NMarks(db)] : INTEGER zum Speichern der Autonummern.
index : Index
26.10 high() - Felddimension ermitteln
Beschreibung
Die tdbengine erlaubt es Arrays mit maximal 10 Dimensionen zu definieren.
Der niedrigste Feldindex ist immer 0, der höchste kann mit HIGH ermittelt werden.
Felder werden immer als Referenzparameter an eigene Prozeduren übergeben, und ebenso immer als sog. offene Felder.
Syntax
HIGH(dim : INTEGER; VAR Array) : INTEGER
Parameter dim : Dimensionsnunmmer
Array : beliebige Array-Variable
Ergebnis
Größtmöglicher Feldindex für die angegebene Dimension.
Beispiel1 High
PROCEDURE FieldTest(VAR a : INTEGER[,]);
VAR max_i : INTEGER = High(1,a);
VAR max_j : INTEGER = High(2,a);
...
ENDPROC
- 273 -
Betriebssystemfunktion
VAR x : INTEGER[10,5000];
VAR y : INTEGER[5,0];
...
FieldTest(x) // max_i=10; max_j=5000
FieldTest(y) // max_i=5; max_j=0
Beispiel 2: InitArray + High
VAR vektor : REAL[]
...
High(1,vektor) // = 0
InitArray(vektor[10000]
High(1,vektor) // = 10000
- 274 -
Betriebssystemfunktion
26.11 Inarray () -prüft, ob Zahl in Zahlenfeld
Beschreibung
Stoppt die Suche bei Feldinhalt 0.
Kann eingesetzt werden, wenn mit PUTMARKS die Markierungliste in einem INTEGER-Feld gespeichert wird.
Wesentlich ist freilich eine Abfrage über ein TBITS-Feld:
Syntax
INARRAY(n : INTEGER; VAR v : INTEGER[]) : 0|1
Parameter
n : beliegige INTEGER-Zahl <> 0
v : INTEGER-Feld
Ergebnis
1 : n ist in v
0 : n ist nicht in v
Beispiel:
VAR temp : TBITS[];
InitArray(temp[filesize(db)]);
GetMarks(db,temp);
IF inarray(x,temp) THEN ...
Hinweis:
Für die Suche nach einem String in ein Array gibt es keine native Funktion.
26.12
inarraypos() - InArray mit Rückgabe der Position
Syntax
INARRAYPOS(n : INTEGER; VAR v : INTEGER[]) : INTEGER
Parameter
n : beliegige INTEGER-Zahl
v : INTEGER-Feld
- 275 -
Betriebssystemfunktion
Ergebnis
Erste Position von n im Array v oder -1 wenn nicht gefunden.
26.13 initarray() - dynamische Felddimensionierung
Beschreibung
Mit dieser Funktion können zur Laufzeit Felder re-dimensioniert werden.Damit sind dynamisch dimensionierte Felder in EASY möglich.
Syntax
INITARRAY(newdim) : INTEGER
Parameter
newdim hat die Form:
array_var[d_1,d_2,...]
Beispiel InitArray + High
VAR vektor : REAL[]
...
High(1,vektor) // = 0
InitArray(vektor[10000]
High(1,vektor) // = 10000
26.14
nbits() - Zählt die gesetzten Bits
Syntax
NBITS(VAR t : TBITS[]) : INTEGER
Parameter
t : Feld von Typ TBITS
Ergebnis
Anzahl der 1-Wert in T
- 276 -
Betriebssystemfunktion
26.15
putmarksauto()
Syntax
PutMarksAuto(db : INTEGER; VAR an : INTEGER[][; index : INTEGER/STRING]) : INTEGER
Parameter
db : Tabellenhandle von OpenDB
an : ARRAY[NMarks(db)] : INTEGER zum Speichern de Autonummern.
index : Index
26.16
strsort() - Sortiert ein- und zweidimensionale STRING-Arrays
Beschreibung
Die Felder werden immer Zeilenweise sortiert.
Mit dem StrSort() wird festgelegt, wie das Feld sortiert wird.
Es besteht aus Spaltennummern, denen ein Minuszeichen vorangestellt werden kann, wenn eine absteigende Sortierung erwünscht ist.
Zusätzlich kann einer Spalte das '%'-Zeichen voangestellt werden, wenn es sich um eine rein numerische Spalte handelt, die dann
entsprechend sortiert wird.
Syntax
STRSORT(StrArray : STRING[]; MaxIndex : INTEGER [;FirstIndex : INTEGER [; SortStr : STRING]]) : INTEGER
Parameter
StrArray : ein oder zweidimensionales STRING-Array
MaxIndex : das Array wird sortiert von FirstIndex..MaxIndex
FirstIndex : Index, ab dem das Feld sortiert wird (0 = Vorgabe)
SortStr : Sortierreihenfolge Ergebnis 0 oder Fehlercode (wenn es sich um kein String-Array handelt)
26.16.1 Sortierung zweidimensionale String-Felder:
StrSort(StringArray,MaxIndex,StartIndex,SortOrder)
Das Feld wird zeilenweise vom StartIndex bis zum EndIndex sortiert.
Wird keine SortOrder angegeben, so wird das Feld aufsteigend nach der ersten Spalte (j=0) sortiert.
SortOrder ist ein String mit folgendem Aufbau:
- 277 -
Betriebssystemfunktion
SpaltenDesc{,SpaltenDesc}
SpaltenDesc=[-][%]Spaltenindex
Wird bei SpaltenDesc ein '-'-Zeichen angegeben, so wird nach dieser Spalte absteigen sortiert, sonst aufsteigend.
Wird bei SpaltenDesc ein '%'-Zeichen angegeben, so wird der Spalteninhalt als Zahl interpretiert und die Spalte numerisch sortiert.
VAR Ergebnis : STRING[10000,10]
...
StrSort(Ergebnis,10000,1,'5,-%8')
Damit wird das Feld "Ergebnis" nach der Spalte mit dem Index 5 (also eigentlich der sechsten Spalte) aufsteigen sortiert. Zeilen mit gleichem Inhalt in dieser
Spalte werden wiederum absteigen nach der Spalte 8 sortiert, wobei diese Spalte numerisch interpretiert wird.
Die Sortierung erfolgt sehr schnell und kann hervorragend eingesetzt werden, wenn Selektionen über mehrere Tabellen erfolgen, wobei eine Ergebnismenge
erzeugt wird.
Dazu noch ein kleines Beispiel, das eine Auswertung von zwei relational verknüpften Tabellen liefert:
VAR db_1 : INTEGER = opendb('database/kunden.dat')
VAR db_2 : INTEGER = opendb('database/bestellung.dat')
VAR query : STRING = '$kunden.KDNR=$bestellung.KDNR, $bestellung.Datum<=today-14'
VAR result : STRING[,]
VAR i,j : INTEGER
InitArray(result[maxlabel(db_1)+maxlabel(db_2),filesize(db_1)*filesize(db_2)]
SUB _query
nloop(i,maxlabel(db_1)-1,result[j,i]:=getfield(db_1,i+1))
nloop(i,maxlabel(db_2)-1,result[j,maxlabel(db_1)+i]:=getfield(db_2,i+1))
j++
ENDSUB
StrSort(result,j-1,0,'1,2')
- 278 -
Betriebssystemfunktion
Beispiel 1: StrSort Kreuzprodukt zweier Tabellen sortiert nach der 1. und 2. Spalte
VAR db_1 : INTEGER = opendb('database/customer.dat')
VAR db_2 : INTEGER = opendb('database/orders.dat')
VAR query : STRING = '$customer.KDNR=$orders.KDNR, $orders.date<=today-14'
VAR result : STRING[,]
VAR i,j : INTEGER
InitArray(result[maxlabel(db_1)+maxlabel(db_2),filesize(db_1)*filesize(db_2)]
SUB _query
nloop(i,maxlabel(db_1)-1,result[j,i]:=getfield(db_1,i+1))
nloop(i,maxlabel(db_2)-1,result[j,maxlabel(db_1)+i]:=getfield(db_2,i+1))
j++
ENDSUB
StrSort(result,j-1,0,'1,2')
- 279 -
Betriebssystemfunktion
- 280 -
Betriebssystemfunktion
27.
Funktionen zum Sessionhandling
27.1
cgigetsession() - Liefert eine Session-ID aus IP-Adresse und Systemzeit
Beschreibung
Eine Session-ID aus CGIGETSESSION hat sich in der Praxis als ausreichend herausgestellt. Der Fall, dass zwei Klienten über die gleiche IP-Adresse zur
gleichen Sekunde einen Funktionsaufruf bewirken, ist unwahrscheinlich.
Um jedoch ganz sicher zu gehen, kann die von CGIGETSESSION gelieferte Session-ID einfach erweitert werden, beispielsweise durch eine Zufallszahl (als
STRING) der eine - bei jedem Aufruf inkrementierte - Nummer (als STRING).
Syntax
CGIGETSESSION
Ergebnis
String mit 16 Zeichen,
1-8 : IP-Nummer des Klieneten als Hex-Zahl
9-16: Sekunden seit dem 1.1.1970 als Hex-Zahl
27.2
setsessionident
Syntax
SetSessionIdent(SessionID, ident, value : STRING) : INTEGER Parameter
Ergebnis
1 : Session wurde erfolgreich gelöscht.
0 : SessionID nicht gültig oder bereits abgelaufen
27.3
delsession
Syntax
DelSession(SessionID : STRING) : INTEGER
Ergebnis
1 : Session wurde erfolgreich gelöscht.
0 : SessionID nicht gültig oder bereits abgelaufen
- 281 -
Betriebssystemfunktion
27.4
cgitestsession() - Testet eine Session-ID auf Gültigkeit
Beschreibung
Besonders bei sensiblen Datenübertragungen sollte eine Session-ID auf Gültigkeit geprüft werden.
CGITESTSESSION liefert zwar keine absolute Sicherheit bei bösartigen Angriffen, schließt aber die Großzahl der "versehentlichen" Zugriffe auf
sessionbezogene Daten wirkunsvoll aus.
Syntax
CGITESTSESSION(SessionID : STRING) : INTEGER
Parameter
SessionID : Eine von CGIGETSESSION gelieferte Session-ID, die jedoch beliebig erweitert sein kann.
Ergebnis
0 : Session nicht gültig wegen nicht übereinstimmender IP-Adresse
sonst: 1+Anzahl der Sekunden, die seit dem Einrichten der Session-ID verstrichen sind
Beispiel 1: CGITestSession
VAR SessionID : STRING
VAR SessionTest : INTEGER
...
SessionID:=CGIGetParam("sessionid")
SessionTest:=CGITestSession(SessionID)
IF SessionTest=0 THEN
// illegal session because of different IP numbers
ELSIF SessionTest>3600 THEN
// TimeOut because this session was valid only for one hour
ELSE
// here it is all right
END
28.
CGI-Funktionen
Funktion
Beschreibung
cgibuffer()
Schaltet die CGI-AusgabepufferSf Schaltet die CGI-Ausgabepufferung an oder aus
cgiclearbuffer() Löscht den Inhalt des ALöscht den Inhalt des Ausgabepuffers
cgiclosebuffer()
Schreibt den Ausgabepunbcnn Schreibt den Ausgabepuffer in die Standardausgabe und beendet Pufferung
- 282 -
Betriebssystemfunktion
cgiexec()
cgigetparam()
cgigetsession()
cgitestparam()
cgitestsession()
cgiwrite()
cgiwritehtml()
cgiwriteln()
cgiwritetemplate()
endcgi()
endsema()
getquerystring()
http_post()
initcgi()
waitsema()
Führt externes Programm aus
Holt einen Parameter aus der Standardeingabe
Liefert eine Session-ID aus IP-Adresse und Systemzeit
Prüft, ob ein Parameter mit der Methode "POST" übertragen wurde
Testet eine Session-ID auf Gültigkeit
Gibt eine Zeichenkette an die Standardausgabe
Gibt eine Zeichenkette im HTML-Format an die Standardausgabe
Gibt eine Zeichenkette + <Zeilenvorschub> an die Standardausgabe
Schreibt den System-Ramtext in die Standardausgabe
Beendet CGI-Funktionalität
Semaphore beenden
Holt einen Wert aus der Environment-Variablen "QUERY_STRING"
RAMText per POST übertragen
Initialisiert die CGI-Umgebung
Setzt eine Sperre
28.1
cgibuffer() - Schaltet die CGI-Ausgabepufferung an oder aus
Beschreibung
Mit dieser Funktion kann die Pufferung für CGI-Ausgaben an- und ausgeschaltet werden.
Nicht zu verwechseln mit CGICloseBuffer!
CGIBuffer gibt den bisherigen Pufferinhalt nicht aus (im Gegensatz zu CGICloseBuffer) und wirkt sich demnach nur auf die folgenden Schreiboperationen aus.
Hinweis: Normalerweise ist die Pufferung der Ausgabe aus Performancegründen immer eingeschaltet. Der Puffer umfasst 64 kByte.
Die Funktion wird vorwiegend dann verwendet, wenn nach einem CGICLOSEBUFFER die Pufferung wieder aktiviert werden soll.
Syntax
CGIBUFFER(mode : INTEGER) : INTEGER
Parameter
mode : 0 (=aus) | 1 (=an)
Ergebnis
Aktueller Modus nach Ausführung der Funktion
CGIBuffer(0) : keine Pufferung
CGIBuffer(1) : Ausgaben werden gepuffert.
28.2
cgiclearbuffer() - Löscht den Inhalt des Ausgabepuffers
Beschreibung
- 283 -
Betriebssystemfunktion
Löscht den Inhalt des Puffers, ohne ihn auszugeben. Die Pufferung wird aber nicht beendet, d.h. alle folgenden Ausgaben gelangen wieder in den Puffer. Diese
Funktion kann verwendet werden, wenn beispielsweise ein Fehler auftritt und statt der bisherigen Ausgaben ein Fehlermeldung erfolgen soll. Ebenso kann ein
anderer Header ausgegeben werden.
Syntax
CGICLEARBUFFER
Ergebnis
0
Beispiel CGIClearBuffer Der Administrator soll noch umgeleitet werden, obwohl der HTTP-Header bereits geschrieben wurde.
CGIWriteLn('content-type: text/html');
CGIWriteLn('');
LoadTemplate(my_template);
IF GetQueryString('command')='start_admin' THEN
CGIClearBuffer;
CGIWriteLn('location: /cgi-tdb/login.prg')
CGIWriteLn('')
END
28.3
cgiclosebuffer() - Schreibt den Ausgabepuffer in die Standardausgabe und Beendet Pufferung
Beschreibung
Schreibt den Ausgabepuffer in die Standardausgabe und beendet Pufferung.
Wenn Sie andere Informationen als Text via CGI übertragen (beispielswseise binäre Daten), sollten Sie vor der Übertragung CGICLOSEBUFFER aufrufen.
Syntax
CGICLOSEBUFFER
Ergebnis
0
Beispiel
cgiwriteln('content-type: image/gif')
cgiwriteln('')
cgiclosebuffer // Header is written
copyfile('pics/mypicture.gif','con') // here picture is transmitted
- 284 -
Betriebssystemfunktion
28.4
cgiexec() - Führt externes Programm aus
Diese Funktion führt ein externes Programm aus. Der Parameter s_timeout wird (vorläufig) nur unter Win9x / WinNT ausgewertet. Das aufgerufene Programm
wird komplett ausgeführt und gibt dann die Kontrolle an das CGI-Programm zurück. Das aufgerufene Programm erhält die Ein- und Ausgabeströme des CGIProgramms. Deshalb sollte vor der Ausführung die Ausgabe-Pufferung beendet werden (da das externe Programm nicht in den tdbengine-Puffer schreiben
kann).
Syntax
CGIEXEC(command : STRING [; timeout : INTEGER) : INTEGER
Parameter
command : Aufruf eines externen Programms inkl. Optionen und Parameter
timeout : maximale Ausführungszeit in s (nur unter Win32)
Ergebnis
0 : Programm wurde ausgeführt
sonst : Programm konnte nicht ausgeführt werden
28.4.1
cgiexec unter Windows
Besonderheiten Win9x, WinNT:
Das Programm wird direkt gestartet. Mit s_timeout kann die maximal verfügbare Zeit (in Sekunden) angegeben werden. Wird dann das Programm vorzeitig
abgebrochen, ist der Funktionswert -1. Wird für s_timeout der Wert -1 angegeben, so wird das Programm zwar komplett ausgeführt, die Ausgabe jedoch
unterdrückt. Diese Vorgegensweise ist der Ausgabeumleitung in jedem Fall vorzuziehen. Die Rechte des ausgeführten Programms werden aus den Rechten des
Internet-Klienten abgeleitet.
- 285 -
Betriebssystemfunktion
Beispiel:richtig: (pkzip25.exe befindet sich in /home/tdbengine/)
CGICloseBuffer .. Puffer leeren
CGIExec('pkzip25.exe -add backup.zip *') .. zeigt die gesamte Liste
.. und erzeugt backup.zip im CGI-Verzeichnis
richtig:(cmd.exe befindet sich in /home/tdbengine/)
...
CGIExec('cmd.exe /c set') .. zeigt das gesamte Environment
falsch: (cmd.exe und pkzip25 befinden sich in /home/tdbengine/)
...
CGIExec('cmd.exe /c pkzip25.exe -add backup.zip * > nul') .. erzeugt backup.zip ohne Ausgabe
richtig: (pkzip25.exe befindet sich in /home/tdbengine/)
CGIExec('pkzip25.exe -add backup.zip *',-1) .. erzeugt backup.zip ohne Ausgabe
Einschränkung: CGIExec kann (zumindest problemlos) nur echte 32-Bit Anwendungen ausführen.
28.4.2
cgiexec unter Linux
Die commandline von CGIExec wird an die Shell des Internet-Klienten mit dessen Rechten übergeben. Hinweis: Hier ist die Ausgabeumleitung der
empfohlene Weg, die Ausgaben des aufgerufenen Programms zu unterdrücken. Beispiel:
CGICloseBuffer .. Puffer leeren
CGIWrite('') .. Ausgabe entsprechend Bildschirm
CGIExec('set') .. zeigt das gesamte Environment
CGIWriteLn('')..
CGIExec('tar -cz -fbackup.tar.gz *') .. erzeugt das Archiv backup.tar.gz mit Ausgabe....
CGIExec('tar -cz -fbackup.tar.gz * > /dev/null') .. erzeugt das Archiv backup.tar.gz ohneAusgabe..
Hinweis: Sie sollten CGIExec immer mit besonderer Vorsicht einsetzen. Unterbinden Sie durch geeignete Semaphoren gleichzeitige Aufrufe (vor allem bei
Archivierungsprogrammen). Bedenken Sie, daß jeder CGI-Prozess nur eine beschränkte Zeit zur Verfügung hat (im http-Server einstellbar).
- 286 -
Betriebssystemfunktion
28.5
cgigetparam() - Holt einen Parameter aus der Standardeingabe
Beschreibung
Die tdbengine bereitet beim Start alle Informationen des Klienten auf: Informationen aus der Environment-Variablen QUERY_STRING werden über die Funktion
QETQUERYSTRING zur Verfügung gestellt.
Informationen aus StdIn werden über CGIGETPARAM aufbereitet.
Alle Ergebnisse liegen als Zeichenkette im Zeichensatz der tdbengine vor.
Die Gross-/Kleinschreibung des Parameters spielt keine Rolle.
Werden mehrere CGI-Variablen mit gleichem Namen übermittelt, so erfolgt der Zugriff folgendermaßen:
1. Variable xyz : CGIGETPARAM("xyz")
2. Variable xyz : CGIGETPARAM("xyz.1")
3. Variable xyz : CGIGETPARAM("xyz.2")
usw.
Eine besondere Rolle spielen in diesem Zusammenhang die Cookies. Diese können mit
CGIGETPARAM("cookie.xyz")
abgeholt werden.
Zugleich liegen die Cookies nach GETENV("HTTP_COOKIE") im Ramtext "ramtext:environment" vor.
Auch der HTML-Typ TEXTAREA wird über die Standardeingabe übertragen. Auf diesen sollte aber nicht über CGIGETPARAM zugegriffen werden, denn erstens
werden dabei nur maximal 255 Zeichen zur Verfügung gestellt, und zweitens wird dabei der Systemramtext "ramtext" überschrieben, wenn der Name einer
solchen CGI-Variablen mit "text" beginnt.
Größere Texte (in HTML-Forumlaren) werden folgendermaßen an das Programm übertragen.
1. Der Name der CGI-Variablen (im HTML-Formular) muss mit "text:" beginnen, beispielsweise
"text:bemerkung".
2. Beim Start stellt dann die tdbengine einen Ramtext mit dem Namen "ramtext:text:..." zur Verfügung, der den gesamten übermittelten Text enthält,
beispielsweise
"ramtext:text:bemerkungen".
- 287 -
Betriebssystemfunktion
Die tdbengine unterstützt auch den Übertragungstyp "multipart/formdata", und damit auch den HTML-Typ "FILE". Beim Start kopiert die tdbengine den
übertragenen Inhalt dieses Feldes (eine Datei) in ein temporäres File, dessen Pfad von CGIGETPARAM geliefert wird, beispielsweise
"634431234/meinedatei.gif". Diese Datei wird beim Programmende automatisch wieder gelöscht. Wenn also die Datei vom Programm akzeptiert wird, muss sie
vom Programm kopiert oder in ein BLOB-Feld eingelesen werden.
Die Übertragungskapazität für Dateien wird meist vom http-Server limitiert. Üblich ist maximale Größe von etwa 100 KByte.
Einfaches Beispiel für CGIGETPARAM:
In einem HTML-Formular stehen folgende Eingabefelder:
Firma:
Internet:
Diese Felder werden vom Anwender ausgefüllt:
Firma: Meine Firma GmbH
Internet: (wird angekreuzt)
Dann liefert
CGIGETPARAM("Firma") : "Meine Firma GmbH"
CGIGETPARAM("Internet") : "1"
- 288 -
Betriebssystemfunktion
Beispiel 1: CGITestSession
VAR SessionID : STRING
VAR SessionTest : INTEGER
...
SessionID:=CGIGetParam("sessionid")
SessionTest:=CGITestSession(SessionID)
IF SessionTest=0 THEN
// illegal session because of different IP numbers
ELSIF SessionTest>3600 THEN
// TimeOut because this session was valid only for one hour
ELSE
// here it is all right
END
Syntax
CGIGETPARAM(ident : STRING):STRING
Parameter
ident : Name der CGI-Variablen
Ergebnis
Wert der CGI-Variablen
28.6
cgitestparam() - Prüft, ob ein Parameter mit der Methode "POST" übertragen wurde
Beschreibung
liefert eine 1, wenn ein CGI-Parameter entsprechenden Namens vom Browser (mit der Methode POST) übertragen wurde, andernfalls 0. Es gilt jedoch zu
beachten, dass bei vielen HTML-Input-Konstrukten (beispielsweise Checkboxen oder Radiobuttons) überhaupt nichts übertragen wird, wenn der Anwender
solche Elemente nicht auswählt.
Syntax
CGITESTPARAM(ident : STRING):0|1
Parameter
ident : Name der CGI-Variablen
- 289 -
Betriebssystemfunktion
Ergebnis
1 : Parameter mit dem Namen wurde übertragen und kann mit CGIGetParam gelesen werden.
0 : Es liegt kein Parameter mit dem Namen vor
28.7
cgiwrite() - Gibt eine Zeichenkette an die Standardausgabe
Beschreibung
Gibt den Parameter unverändert an die Standardausgabe weiter.
Falls die CGI-Ausgabepufferung aktiv ist, erfolgt die Ausgabe erst, wenn der Puffer voll ist oder das Programm beendet wird.
Syntax
CGIWRITE(S : STRING) : INTEGER
Parameter
S : beliebige Zeichenkette
Ergebnis
0, alles in Ordnung, sonst Fehlercode
28.8
cgiwritehtml() - Gibt eine Zeichenkette im HTML-Format an die Standardausgabe
Beschreibung
Gibt eine Zeichenkette im HTML-Format an die Standardausgabe. Dabei werden alle Sonderzeichen entsprechend der HTML-Codierungstabelle gewandelt.
Beispiel: Müller
Diese Funktion ist bei längeren String der Kombination CGIWRITE(TOHTML(S)) vorzuziehen, da dabei TOHTML zur Überschreitung der maximalen Länge von
Zeichenketten (255 Zeichen) führen kann.
Falls die CGI-Ausgabepufferung aktiv ist, erfolgt die Ausgabe erst, wenn der Puffer voll ist oder das Programm beendet wird.
- 290 -
Betriebssystemfunktion
Syntax
CGIWRITEHTML(S : STRING) : INTEGER
Parameter
S : beliebige Zeichenkette
Ergebnis 0 : alles in Ordnung
sonst : Fehlercode
28.9
cgiwriteln() - Gibt eine Zeichenkette + <Zeilenvorschub> an die Standardausgabe
Beschreibung
Gibt den Parameter ergänzt um CR/LF an die Standardausgabe weiter.
Falls die CGI-Ausgabepufferung aktiv ist, erfolgt die Ausgabe erst, wenn der Puffer voll ist oder das Programm beendet wird.
Syntax
CGIWRITELN(S : STRING) : INTEGER
Parameter
S : beliebige Zeichenkette
Ergebnis
0, alles in Ordnung, sonst Fehlercode
28.10 cgiwritetemplate() - Schreibt den System-Ramtext in die Standardausgabe
Beschreibung
Überträgt den System-Ramtext "ramtext" in die Standardausgabe.
Falls die CGI-Ausgabepufferung aktiv ist, erfolgt die Ausgabe erst, wenn der Puffer voll ist oder das Programm beendet wird.
Die übliche Vorgehensweise bei der Arbeit mit Templates:
LoadTemplate("vorlagen/eingabeformular.html")
Subst('#Name#',adressen,'Name')
Subst('#Vorname#',adressen,'Vorname')
...
CGIWriteTemplate
- 291 -
Betriebssystemfunktion
Syntax
CGIWRITETEMPLATE
Ergebnis
0 : Alles in Ordnung, sonst : Fehlercode
28.11 endcgi() - Beendet CGI-Funktionalität
Beschreibung
Der CGI-Puffer wird an den Klienten übertragen und der Semaphor freigegeben.
Zudem versucht das Programm dem http-Server mitzuteilen, dass keine weiteren Ausgaben mehr erfolgen (er also die Verbindung zum Klienten beenden kann).
Das funktioniert leider derzeit mit keinem der bekannten http-Server.
Syntax
ENDCGI
28.12
endsema() - Semaphore beenden
Syntax
ENDSEMA([sema : INTEGER])
Parameter
sema : Handle oder 0 für alle (sonst System-Semaphore)
28.13 getquerystring() - Holt einen Wert aus der Environment-Variablen "QUERY_STRING"
Beschreibung
Die Environment-Variable QUERY_STRING wird so übertragen:
Variable1=Wert1&Variable2=Wert2&...
Die gesamte Variable ist URL-codiert.
Bei dem Zugriff über GETQUERYSTRING wird diese Variable automatisch URL-decodiert, so dass man direkt mit den Werten arbeiten kann.
- 292 -
Betriebssystemfunktion
Beispiel:
Aus folgender URL
http://meinhost.de/cgi-tdb/test.rg?name=Meier&vorname=Hans
liefert
GETQUERYSTRING('name') : Meier
GETQUERYSTRING('vorname') :Hans
GETQUERYSTRING('sonstiges') : ''
Syntax
GETQUERYSTRING(id : STRING) : STRING
Parameter
id : Name der CGI-Variablen
Ergebnis
Wert der CGI-Variablen, falls vorhanden, sonst Leerstring
28.14
http_post() - RAMText per POST übertragen
Syntax
HTTP_POST(f_f_from,to,post : STRING) : INTEGER
Parameter
f_from: URL
f_to: RAMText, in dem das Ergebnis der Anfrage abgelegt wird.
f_post: RAMText mit POST-request, inklusive header
Ergebnis
0: erfolgreich, sonst: Fehlernummer
Zur Info: 18.12.2012/hg
http_post baut eine Socketverbindung auf.
Kann diese nicht aufgebaut werden, so liefert http_post sofort einen Fehlercode zurück.
Wenn die Socketverbindung steht und die Gegenseite nicht antwortet, dann bleibt die Verbindung stehen.
- 293 -
Betriebssystemfunktion
Daher muss man notfalls mit timeout die Dauer der Socketverbindung terminieren.
28.15 initcgi() - Initialisiert die CGI-Umgebung
Beschreibung
InitCGI() wird normalerweise automatisch beim Start der tdbengine ausgführt.
Wichtigste Arbeit: Initialisierung der CGI-Variablen und Cookies, die via POST an den Server geschickt werden. Variablen und Cookies werden in einer
baumartigen Struktur gespeichert.
Normalerweise greift InitCGI dabei auf die Standardeingabe zu (wie es laut CGI-Spezifikation sein muss). In einer Server-Umgebung, also wenn die Funktion
Server() ausgeführt wird, greift InitCGI() auf den gerade aktiven Socket zu.
Wenn nun mit der tdbengine ein nicht-forkender Server gebastelt wird, muss dies vor jedem Aufruf eines CGI-Programms explizit erfolgen.
Zudem werden beim normalen Beenden der tdbengine sämtliche Dateien geschlossen. Auch das macht InitCGI.
sScript ist der Parameter, den ParamStr(0) des aufgerufenen Programms zurückliefert. Dieser wird normalerweise aus der Environment-Variablen "PATH_INFO"
oder "SCRIPT_NAME" gewonnen.
Syntax
INITCGI(sScript : STRING)
Parameter
sScript ist der Pfad zum Programm
28.16 waitsema() - Setzt eine Sperre
Beschreibung
Die tdbengine setzt keine Sperren auf Tabellen, sie werden immer im Shared-Mode geöffnet.
Um einen sicheren Multiuser-Betrieb zu gewährleisten, stellt die tdbengine 255 binäre Semaphoren zur Verfügung, die derzeit als (leere, gesperrte) Dateien
realisiert sind.
Eine nähere Beschreibung finden Sie auch in der Dokumentation (Stichwort: Semaphorenkonzept).
Syntax
WAITSEMA(fn : STRING; timeout : INTEGER) : INTEGER
Parameter
fn : Dateiname des Semaphoren. Vorangestellt wird semadir, es sei denn, fn enthält Pfadtrennzeichen.
timeout : Wartezeit in ms
Ergebnis
>0 : Handle
0 : Semaphor konnte nicht geöffnet werden
- 294 -
Betriebssystemfunktion
29.
Datums/Uhrzeit-Funktionen
29.1
Alle Datums- und Zeitfunktionen
Funktion Beschreibung
datestr()
Datum als Zeichenkette
2
datetime_to_unix()
Macht aus einer Datums- und Zeitangabe einen Unix-Timestamp
day()
Extrahiert die Tageszahl aus einem Datum
dayofweek()
Wochentag eines Datums
monat()
Extrahiert den Monat aus einem Datum (veraltet)
month()
Monat aus einem Datum
now()
Systemzeit
str_to_unixtime()
Macht aus einem String einen Unix-Timestamp
timestr()
Zeitangabe als STRING
unixtime_to_str()
Macht aus einem Unix-Timestamp eine String
unix_date()
Liefert das Datum aus einem Unix-Timestamp
unix_now()
Liefert den Unix-Timestamp des Systemdatums
unix_time)
Liefert die Uhrzeit aus einem Unix-Timestamp
week()
Kalenderwoche
year()
extrahiert die Jahreszahl aus einem Datum
29.2
datestr() - Datum als Zeichenkette
Syntax
2
Unter einem Unix-Timestamp versteht man die Anzahl der Sekunden, die seit Beginn der Unix-Epoche am 1.1.1970 vergangen sind. Werden diese in einer 32Bit Variablen gespeichert, so ist damit der Bereich von 1970 bis etwa Anfang 2038 abgedeckt. Unix-Timestamps eignen sich sehr gut, Update-Zeiten für
Datensätze festzuhalten, wenn eine sekundengenaue Auflösung ausreicht (was in den meisten Fällen wohl zutrifft).
Konstanten vom Typ Unix-Timestamp werden in der Form
DD.MM.YYYY_hh:mm:ss
angegeben (DD = Tag, MM = Monat, YYYY = Jahr, hh = Stunden, mm = Minuten, ss = Sekunden). Wichtig ist hierbei der Unterstrich zwischen Datums- und
Zeitangabe.
- 295 -
Betriebssystemfunktion
DATESTR(date : INTEGER) : STRING
Parameter
date : Datum im Format der tdbengine (Tage seit 1.1.0)
Ergebnis
Zeichenkette der Form tt.mm.jjjj
Beispiel
DATESTR(21.5.1952) -> "21.05.1952"
DATESTR(TODAY) -> aktuelles Datum als Zeichenkette
DATESTR(0) -> "" (Sonderfall)
29.3
datetime_to_unix() - Macht aus einer Datums- und Zeitangabe einen Unix-Timestamp
Syntax
DATETIME_TO_UNIX(date : INTEGER; time : REAL) : INTEGE
Parameter
date : Datum im TDB-Format
time: Zeit im TDB-Format
Ergebnis
Anzahl der vergangenen Sekunden zwischen dem 1.1.1970 und der angegebenen Datum/Zeit-Kombination
Beispiel:
DATETIME_TO_UNIX(1.1.1970,0:0:0) = 0
DATETIME_TO_UNIX(today,now) = 1067089596
Hinweis:
Auf Windows-Systemen findet keine Berücksichtung von Zeitzone und Sommer-/Winterzeit statt.
Auf Linuxservern holt sich die tdbengine die Anzeige der Serverzeit über einen Aufruf ans Betriebssystem.
Dabei übergibt Sie eine Zahl und bekommt den Anzeigetext zurück.
Der Server holt diese von der eingestellten Serverzeit.
Ist diese auf Sommerzeit umgestellt, so ist dieser Anzeigewert um die entsprechende Zeit verschoben.
Korrekt wäre es, wenn man die UTC Zeit als Referenz heranziehen könnte.
Diese wird aber bei dem Systemaufruf nicht benutzt.
- 296 -
Betriebssystemfunktion
29.4
day() - Extrahiert die Tageszahl aus einem Datum
Syntax
DAY(date : INTEGER) : INTEGER
Parameter
date : Datum im Format der tdbengine (Tage seit 1.1.0)
Ergebnis
Tageszahl aus dem Datum
Beispiel
DAY(15.2.2000)
Egebnis:15
29.5
dayofweek() - Wochentag eines Datums
Syntax
DAYOFWEEK(date : INTEGER) : STRING
Parameter
date : Datum im Format der tdbengine (Tage seit 1.1.0)
Ergebnis
Wochentag des Datums (deutsch)
"Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"
Beispiel1
"Heute ist "+DAYOFWEEK(TODAY)+", der "+DATESTR(TODAY)
//Ergebnis:Heute ist Dienstag, der 13.06.2000
Den Ordinalwert des Wochentags erhalten Sie mit der Formel: 1+(date+4) MOD 7
Beispiel2
- 297 -
Betriebssystemfunktion
PROCEDURE DayOfWeek(date : INTEGER) : STRING
VAR w_day : INTEGER w_day:=1+(date+4) MOD 7
IF w_day=1 THEN RETURN "monday"
ELSIF w_day=2 THEN RETURN "tuesday"
ELSIF w_day=3 THEN RETURN "wednesday"
...
// oder kürzer
// RETURN choice(w_day,"mo","di","mi","do","fr","sa","so")
END
ENDPROC
29.6
monat() -Extrahiert den Monat aus einem Datum (veraltet)
Beschreibung
Wird durch MONTH ersetzt
Syntax
MONAT(d : INTEGER) : INTEGER
Parameter
d : Datum
Ergebnis
Monat des Datums
- 298 -
Betriebssystemfunktion
29.7
month() - Monat aus einem Datum
Syntax
MONTH(d : INTEGER) : INTEGER
Parameter
d : Datum
Ergebnis
Monatszahl des Datums
Beispiel:
MONTH(21.5.1952) = 5
29.8
now() – Systemzeit
Syntax
NOW : REAL
Ergebnis
Anzahl der seit Mitternacht vergangenen Minuten. Im Nachkommabereich sind Sekunden und Sekundenbruchteile enthalten.
Beispiel
TIMESTR(NOW,3)
29.9
//liefert die Systemzeit als String mit Millisekunden-Auflösung
str_to_unixtime() - Macht aus einem String einen Unix-Timestamp
Syntax
STR_TO_UNIXTIME(datetimestr : STRING) : INTEGER
Parameter
datetimestr : STRING der Form dd.mm.yyyy[_hh[:mm[:ss]]]
Ergebnis
- 299 -
Betriebssystemfunktion
Unix-Timestamp des übergebenen Strings
Beispiel:
STR_TO_UNIXTIME('18.11.2003') 1069110000
( entspricht 18.11.2003_00:00:00)
STR_TO_UNIXTIME('18.11.2003_09:25:49')
1069143949
Hinweis: Auf Windows-Systemen findet keine Berücksichtung von Zeitzone und Sommer-/Winterzeit statt.
29.10 timestr() - Zeitangabe als STRING
Beschreibung
Es gibt aber auch noch erweiterte Formen:
TIMESTR(NOW,0) -> 'hh:mm:ss'
TIMESTR(NOW,n) -> 'hh.mm:ss.x' mit x als Sekundenbruchteilen (n Stellen)
Syntax
TIMESTR(Minuten_seit_Mitternacht : REAL) : STRING
Parameter
Minuten_seit_Mitternacht : Kommazahl (Minuten)
Ergebnis
String der Form 'hh:mm'
Zum Speichern von Unix-Timestamps gibt es den neuen Datentyp "UTIME", der eine 32-Bit-Zahl speichert. GetField und SetField konvertieren derartige Inhalte
automatisch:
SetField(db,'edit_date','18.11.2003_14:12:52')
GetField(db,'edit_date') -> "18.11.2003_14:12:52"
Scheller wird es freilich, wenn auf Unix-Timestamps mit den Funktionen SetRField und GetRField zugegriffen wird.
SetRField(db,'edit_date',UNIX_NOW)
IF UNIX_NOW-getrfield(db,'edit_date')<3600 THEN ...
- 300 -
Betriebssystemfunktion
// Datensatz wurde innerhalb der letzten Stunde editiert...
- 301 -
Betriebssystemfunktion
30.
CodierungundVerschlüsselung
DieBase64-Codierung ist der Standard bei derE-Mail-Übertragung von binären Dateien.Dabei werden aus drei Quell-Bytes vier Ziel-Bytes erzeugt, wobei die
Zielbytes allesamt Zeichen im druckbaren Bereich ergeben.
EncodeB64(s : STRING) : STRING // verschüsselt einen String mit Base64
DecodeB64(s : STRING) : STRING // entschlüsselt einen String mit Base64
PROCEDUREMain
VARs_in,s_out,s_test:STRING
cgiwriteln('content-type:text/plain')
cgiwriteln('')
s_in:='UlrichAndreasKern'
s_out:=EncodeB64(s_in)
s_test:=DecodeB64(s_out)
cgiwriteln(s_in)
cgiwriteln(s_out)
cgiwriteln(s_test)
ENDPROC
Ausgabe:
UlrichAndreasKern
VWxyaWNoIEFuZHJlYXMgS2Vybg==
UlrichAndreasKern
30.1
RoutinenzurPasswort-Behandlung
Die tdbengine stellt zwei Routinen aus der Crypt-Bibliothek zur Verfügung:
MakePW(s:STRING):STRING//verschlüsselt ein Passwort im Unix-Standard
TestPW(s,vgl:STRING):0|1//prüft, ob vgl eine Verschlüsselung vorhanden ist
- 302 -
Betriebssystemfunktion
30.2
Passwortabfrage über ein Anmeldeformular gegen einen Eintrag in einer TDB-Tabelle
Wir haben eine user.dat im Verzeichnis /sysbase liegen, die DEF-Datei hat folgenden Aufbau:
Über ein Eingabeformular möchten wir Benutzername und Passwort abfragen:
Das Loginformular
wird in dojoform.dat wie links abgebildet definiert:
Anschließend wird das Formular aufgerufen und der Anwender gibt Username und Passwort ein:
- 303 -
Betriebssystemfunktion
Der Client schickt nun das Formular mit folgendem Aufruf zum Server: ../program/login.prg?act=checkLogin.
Serverseitig muss login.prg exisitieren und die Procedure checkLogin übernimmt die Prüfung des Zugangs.
Die folgende Procedure überprüft den Loginnamen und das Passwort:
Procedure checkLogin
..11.10.2011 12:19:24/hg
Var user : String = GP("Username")
Var pw
: String = GP("Passwort")
Var U : Integer = OpenDB(sPath+"sysbase/user.dat","",0,15)
If ReadRec(U, FindRec(U,user,"$Loginname",1))
If TestPW(pw,GetField(U,"PW"))
TraceLog(0,'login.prg:PW ok')
Else
TraceLog(0,'login.prg:PW falsch')
End
Else
TraceLog(0,'login.prg:Loginname falsch')
End
EndProc
MakePW verschlüsselt eine Zeichenkette.
Zum Beispiel wird aus dem Passwort „123qwe“ der folgende Eintrag im Feld Passwort
- 304 -
Betriebssystemfunktion
Damit kann man sicherstellen, dass in einer TDB-Tabelle hinterlegte Passwörter über den baseman nicht ausgelesen werden können.
30.3
Passwortabfrage über ein Anmeldeformular gegen einen Eintrag in htaccess (Linux only)
Unter Linux besteht die Möglichkeit, auf ein Verzeichnis einen Dialog mit Username und Passwort zu legen.
Damit kann man verhindern, dass jeder beliebige User auf dieses Verzeichnis zugreifen kann.
Benutzername und Passwort wird dabei in der Datei .htpasswd abgelegt (siehe die folgende Abbildung)
Das untere Programmbespiel prüft die Anmeldedaten gegen die Einträge in htaccess:
PROCEDURE TestUser(file, id, pw : STRING) : INTEGER
/* Funktion zur Überprüfung einer id-pw-Kombination.
file: passwd-Datei, die mit htpasswd (Apache) unter Linux erzeugt wurde
Rückgabewert:1:Userokay
0:Nomatch
*/
VARt:INTEGER=reset(file)
VARl,vgl:STRING
WHILE NOT EOT(t) DO
IFpos(id+':',l:=readln(t)) THEN
close(t)
IFtestpw(pw,l[length(id)+2,255]) THEN
RETURN 1
ELSE
RETURN 0
- 305 -
Betriebssystemfunktion
END
END
END
close(t)
RETURN0
ENDPROC
- 306 -
Betriebssystemfunktion
31.
Debugging
31.1
Fehlerpositionen aufspüren
Falls ein CGI-Programm seine Arbeit vorzeitig beendet (abstürzt), aber auch, wenn ein interner Fehler auftritt (beispielsweise bei VAL), schreibt die tdbengine
einen entsprechenden Eintrag in die Datei error.msg im Verzeichnis der tdbengine (falls dort ein Schreibrecht eingerichtet wurde). Zusätzlich zur Fehlerart wird
die Position des Programms ausgegeben. Sie können die Fehlerstelle im Quelltext bestimmen, wenn Sie auf der Kommandoebene compilieren und die
Fehleradresse als zweites Argument angeben:
tdbengine test.mod 12345
Das Programm test.mod wird compiliert, und solange die Fehlerstelle nicht ereicht ist, wird der Quelltext ausgegeben. Die Fehlerstelle befindet sich also in er
letzten ausgegebenen Zeile.
31.2
Programmaufruf tracen
Ab der tdbengine 7.02 gibt es die Möglichkeit, einen Aufruf zu tracen und gleichzeitig auch die Performance zu protokollieren.
Legen Sie hier einen Eintrag unter trace_time= an.
[adressen]
log=/var/www/develop/address/logfiles/adressen.log
trace_time=/var/www/develop/address/logfiles/adressen.log
Hier ein Beispiel für ein solchen Logfile, welche über den folgenden Eintrag definiert wurde:
[auftragsliste]
log=/var/www/develop/zmm/logfiles/auftragsliste.log
trace_time=/var/www/develop/zmm/logfiles/auftragslisteTime.log
Das Protokoll enthält 2 Bereiche:
[time] => zeigt die Reihenfolge aller internen Proceduraufrufe mit den jeweiligen Zeiten in Milli-Sekunden.
[calls] => zeigt wie oft eine Funktion insgesamt aufgerufen wurde.
- 307 -
Betriebssystemfunktion
[time]
session_ermitteln|GP=0
session_ermitteln=0
Main|GIs|cSessionpfad|IsZahl=0
Main|GIs|cSessionpfad=0
Main|GIs=0
Main|TI_NL_check|OpenDB=0
Main|TI_NL_check|CloseDB=0
Main|TI_NL_check=1
Main|sbiShowDialog=0
Main|Datenbankcheck|GP=0
Main|Datenbankcheck|sessionueberpruefen|GP=0
Main|Datenbankcheck|sessionueberpruefen|OpenDB=2
..Einträge gelöscht, da sonst zu lang
Main|Auftragsliste_Suchen|Statistik_erzeugen=27042
Main|Auftragsliste_Suchen=27145
Main|Programm_austragen|OpenDB=2
Main|Programm_austragen|CloseDB=1
Main|Programm_austragen|SetIdent=2
Main|Programm_austragen=12
Main|sbiHideDialog=1
Main=35266
- 308 -
Betriebssystemfunktion
Und ab wird angezeigt, wie oft eine Procedure aufgerufen wurde.
[call]
session_ermitteln|GP=1
session_ermitteln=1
Main|GIs|cSessionpfad|IsZahl=2
Main|GIs|cSessionpfad=2
Main|GIs=2
Main|TI_NL_check|OpenDB=1
Main|TI_NL_check|CloseDB=1
Main|TI_NL_check=1
Main|sbiShowDialog=1
Main|Datenbankcheck|GP=1
Main|Datenbankcheck|sessionueberpruefen|GP=1
Main|Datenbankcheck|sessionueberpruefen|OpenDB=7
Main|Datenbankcheck|sessionueberpruefen|CloseDB=7
..Einträge gelöscht, da sonst zu lang
Main|Auftragsliste_Suchen|Statistik_erzeugen=1
Main|Auftragsliste_Suchen=1
Main|Programm_austragen|OpenDB=1
Main|Programm_austragen|CloseDB=1
Main|Programm_austragen|SetIdent=1
Main|Programm_austragen=1
Main|sbiHideDialog=1
Main=1
31.3
Erweiterte Information in Log-Datei schreiben - setcgilog()
Syntax
SETCGILOG(S : STRING) : INTEGER
Parameter
S : STRING (beliebig)
Ergebnis
immer 0
- 309 -
Betriebssystemfunktion
Beschreibung
Wenn in tdbengine.ini unter
[globals]
logcgi=1
steht, so erfolgt bei jedem Aufruf ein Eintrag in die Datei cgi.log.
Dabei werden Datum, Zeit, Remote_IP, Laufzeit und Programmname protokolliert.
Mit SetCGILog kann ein Programm diese Informationen ergänzen.
Beispiel:
SetCGILog('Beta-Version des Programms')
- 310 -
Betriebssystemfunktion
32.
ALLGEMEINE FEHLERCODES UND -MELDUNGEN
32.1
Fehlercode 13: Index passt nicht zur Datei (16.07.2013)
Die Meldung „Index paßt nicht zur Datei“ wird unter folgenden Umständen generiert:
Ein Prozess schreibt auf die Tabelle, im Dateiheader der .dat-Datei wird eine neue Dateigröße (= Anzahl der Datensätze ) eingetragen.
Die dazugehörigen Index Dateien sind aber zu diesem Zeitpunkt noch nicht geschrieben.
Erfolgt nun ein lesender Zugriff auf den Index, so stellt die tdbengine fest, dass die Anzahl Datensätze, die in der Dat - Datei eingetragen sind nicht in identisch
sind mit der Anzahl Datensätze, die im Dateiheader des Index stehen.
Daraufhin wird die Fehlermeldung generiert.
Die Fehlermeldung ist nicht so zu interpretieren, dass der Index tatsächlich defekt ist. Andere strukturieren der Tabelle ist daher nicht notwendig.
Beim Öffnen einer Datei zum Lesen wird kurz geprüft, ob alle Indexdateien zur Datengröße passen.
Ist dies nicht der Fall, so wird der folgende Fehler ins error.log eingetragen:
Beispiel:
16.07.2013 14:41:02.40: 13 (Index paßt nicht zur Datei: ) reise_termine.id in /var/www/develop/pedalo/xhr/reise_termine.prg(20288)
16.07.2013 14:41:02.40: 13 (Index paßt nicht zur Datei: ) reise_termine.in1 in /var/www/develop/pedalo/xhr/reise_termine.prg(20288)
16.07.2013 14:41:02.40: 13 (Index paßt nicht zur Datei: ) reise_termine.in2 in /var/www/develop/pedalo/xhr/reise_termine.prg(20288)
Das Programm wird jedoch nicht abgebrochen und der Index auch nicht automatisch repariert.
Das passiert nur dann, wenn die Datei auch zum Schreiben geöffnet wird.
Welche Konsequenzen hat ein defekter Index?
Fraglich ist jedoch, welches Ergebnis der lesende Zugriff auf einen Index liefert, der noch im Aufbau ist. Möglicherweise ist der Index schon vollständig
geschrieben und das Ergebnis einer Suche oder eines Filters ist korrekt. Wenn der Index jedoch noch im Aufbau ist, so kann es sein, dass die Suche über
FindRec oder über eine FindAndMark Anweisung ein solches Ergebnis liefert.
Die Suche nach Einträgen erfolgt in der Regel immer anhand des Index. Ist der Index defekt, so wird möglicherweise ein Datensatz nicht gefunden,
obwohl er eigentlich exisitert.
Richtig ungut ist dies, wenn geprüft wird, ob der Datensatz bereits in der Tabelle vorhanden ist und der Datensatz aufgrund dieser Situation nicht gefunden wird.
- 311 -
Betriebssystemfunktion
32.2
Index defekt
Diese Meldung weißt darauf hin, dass beim Zugriff auf einen Index festgestellt wurde, dass der Index defekt ist. Dies wird also nicht festgestellt, wenn die Tabelle
nur geöffnet wird.
Der Index wird in keinem Fall automatisch repariert, hier hilft nur eine manuelle Auffrischung des Index über z.B. regenall.
32.3
Code 188: Unbekannter IO/Fehler (23.01.2012)
Beispiel:
13.01.2012 07:38:21.10: 188 (unbekannter IO-Fehler: ) 5895/5440 in /var/www/html/test.prg(0)
Der erste Wert gibt an, wie viele Bytes bei einem POST-Request erwarten werden.
Der 2. Wert gibt an, wieviele Bytes tatsächlich angekommen sind.
Die tdbengine bricht mit der sichtbaren Fehlermeldung ab, wenn die Content length nicht mit der tatsächlich erhalten Dateigröße übereinstimmt.
Wenn dies z.B. im Umfeld von global remote geschieht, so kann die Ursache daran liegen, dass global remote nach x Byte abschneidet
32.4
Noch unbearbeitet
-2 : Das angegebene Module wurde nicht gefunden
Problem:Dieser Fehler tritt beim Versuch, ein nicht auffindbares Modul zu kompilieren,auf.
@home>/usr/bin/tdbengineerror1.mod
Module:error1.mod
error1.mod,compilererror:-2
Lesefehler
0/0
Lösung: Überprüfung der Pfad- und Dateiangaben auf Tippfehler.
2:Das angegebene Programm wurde nicht gefunden
- 312 -
Betriebssystemfunktion
Problem:Dieser Fehler tritt auf, wenn das aufzurufende PRG nicht existiert.
@home> /usr/bin/tdbengine error2.prg
content-type: text/html
<h2>tdbengine (v 6.2.9) message</h2>
<h3>error 2 in module: error2.prg</h3>
Lösung: der Pfadangaben auf Tippfehler oder ob das Modul überhaupt kompiliert wurde.
32.5
Compiler und Interpreterfehler – 10.10.2011
Das sind Fehler die bereits während des Kompiliervorgangs oder auch erst zur Laufzeit von der tdbengine erkannt werden. Diese Fehler werden, sollten sie
auftreten, normalerweise auch im error.log erfasst.
Diese Fehler können auch mit TDB_LastError selbst abgefangen werden, wenn zuvor die automatische Fehlerbehandlung mit SetPara abgeschalten wurde.
Code
Meldung
1
Datei kann nicht
geöffnet werden
2
allgemeiner
Lesefehler
3
allgemeiner
Schreibfehler
4
Fehler bei
Indexerstellung
5
Fehler bei
Indexlöschung
Mögliche Ursache
Mögliche Abhilfe
Tritt möglicherweise auf, wenn die Tabelle mit Cleardat
geleert wurde. Ein anschließend regenall scheint auch
nicht wirklich die Indexdatei nach der Leerung der Tabelle
wieder vollständig zurückzusetzen.
Diese Meldung hat aber auch möglicherweise nichts mit
dem Index zutun. Der Fehlercode wurde doppelt belegt
und verweist dann auf einen derzeit nicht dokumentierten
Fehler.
Restrukturieren der Tabelle (mit dem baseman.prg).
- 313 -
Betriebssystemfunktion
6
Index nicht
vorhanden
7
Fehler in Selektion
8
Import-Fehler (nicht
tdbengine)
9
Fehler beim
Dateierzeugen (IOFehler)
10
Fehler beim
Dateischließen (IOFehler)
11
Indexschlüssel zu
groß (über 256
Bytes)
12
Fehler beim
Indexladen (IOFehler)
13
Index paßt nicht
zur Datei
(ungleiche Anzahl
von Records)
14
Kein Datenfeld
(illegaler
Feldbezeichner)
15
Illegales
Unterverzeichnis
(nicht vorhanden
oder kein
Zugriffsrecht)
16
Illegale
Dateiverknüpfung
(Fehler in
RELATION)
17
Datei ist offen
- 314 -
Betriebssystemfunktion
(opendb auf bereits
geöffnete Tabelle)
18
Keine Datei im
aktuellen
Verzeichnis
19
Datensatz nicht
gefunden
20
Modul nicht
gefunden (bei
USES oder
INCLUDE)
22
Datei existiert
bereits
23
Index bereits
geöffnet
25
allgemeiner
Syntax-Fehler
26
Illegaler Dateiname
28
Indexbeschreibung
zerstört
29
Falsche
Programmversion
30
doppelter Eintrag
31
MEMO-Datei nicht
zu öffnen
32
MEMO hier nicht
erlaubt
33
Fehler bei
Memoschreiben
34
Illegale Operation
35
Datei bereits
geöffnet
- 315 -
Betriebssystemfunktion
36
Zugriff nicht erlaubt
37
Datei nicht
gefunden oder
Zugriff verweigert
(bei opendb)
38
Falscher Code
(Passwort oder
Verschlüsselungsc
ode passen nicht)
39
Fehler in
INCLUDE-Datei
40
Kein angekoppelter
Datensatz
41
Ausdruck nicht
komplett
42
Illegaler Operator
44
Typen stimmen
nicht überein oder
unbekannter Typ
(meist bei
Zuweisungen
A:=B)
45
Unbekanntes
Zeichen
46
Illegale
Zahlenkonstante
48
Logischer Operand
fehlt
49
Illegaler Operand
50
Unbekannter
Bezeichner
51
Feldvariable
erwartet
- 316 -
Betriebssystemfunktion
52
Unbekannter
Fehler
53
Zu viele Variablen
54
'=' fehlt
55
Zahl erwartet
56
? erwartet
57
Illegale
Dateistruktur
58
Keine Tabelle
59
Zu viele Felder
(mehr als 1000
Datenfelder in
Tabelle)
60
Ausdruck zu
komplex (bspw. in
Rekursion)
61
Fehler in Ausdruck
66
Zeichenkette
erwartet
67
Zu viele Indizes
71
Bezeichner doppelt
definiert
72
REL-Datei existiert
bereits (bei
GENREL)
73
Zu viele L- oder RFelder (bei
MAKEDB)
74
String zu lang
(mehr als 255
Zeichen)
76
Nur ein N-Feld
- 317 -
Betriebssystemfunktion
erlaubt (ein AUTOFeld pro Tabelle)
77
Keine
Relationstabelle
(bei MARKTABLE)
80
Nur ein ID_Index
erlaubt
81
Bereichsüberlauf
(bei ARRAYIndizes)
82
Unbekanntes
Kommando
83
Label fehlt
84
Kein ID-Index
festgelegt
85
Keine Definition
(Tabellenstruktur
ohne Felder)
86
Illegale
Textaufteilung
87
Illegaler Zugriff (bei
ACCESS)
89
Variable erwartet
(bei
Referenzparameter
n)
90
Fehler beim
Indexschreiben
91
Fehler beim
Indexlesen
92
Ende des
Unterreports nicht
festgelegt
- 318 -
Betriebssystemfunktion
(ENDSUB fehlt)
93
Illegale Operation
94
Zu viele Dateien
(mehr als 31
geöffnete Tabellen)
95
Index defekt
96
UNTIL erwartet
97
END erwartet
98
Illegale
Indexdefinition
(auch bei
SORTMARK)
101
Verschachtelung
nicht erlaubt (von
Porzeduren)
102
ENDPROC fehlt
103
Weitere Anwender
im Netz (nur bei
Spezialversion)
104
Netzoperation
abgebrochen (nur
bei Spezialversion)
105
Datensatz wird
bereits editiert (nur
bei Spezialversion)
106
Fehler beim
Einloggen (nur bei
Spezialversion)
107
IPX-nicht aktiv (nur
bei Spezialversion)
108
Timeout-Fehler
(nur bei
Spezialversion)
- 319 -
Betriebssystemfunktion
109
Unbekannter
Fehler im Netz (nur
bei Spezialversion)
110
Locking-Fehler (nur
bei Spezialversion)
111
BLOB nicht zu
öffnen
112
MEMO defekt
113
BLOB defekt
114
Operation
abgebrochen
115
Kein Schreibrecht
auf Datei
116
Index noch in
Gebrauch
117
Illegale
Dateistruktur
120
Laufzeitfehler (für
Debugger)
32.5.1
Var Name(VAR x) : STRING
Diese Funktion liefert der Deklarationsnamen der Variablen x. Damit kann man recht schöne Sachen machen, wie zum Beispiel eine Menge Code einsparen.
Gut:
VARDEF Name, Vorname, Strasse, PLZ, Ort:STRING
...
- 320 -
Betriebssystemfunktion
Name:=CgiGetParam("Name")
Vorname:=CgiGetParam("Vorname")
...
Besser:
VARDEF Name, Vorname, Strasse, PLZ, Ort:STRING
...
Name:=CgiGetParam(VarName(Name))
Vorname:=CgiGetParam(VarName(Vornam))
...
…
Noch besser:
VARDEF Name, Vorname, Strasse, PLZ, Ort : STRING
...
PROCEDURE CgiGetVar(VAR x : STRING)
x:=CgiGetParam(VarName(x))
ENDPROC
...
CgiGetVar(Name)
CgiGetVar(Vorname)
....
32.5.2
TestSel(selection : STRING) : REAL
Liefert 0, wenn es sich bei dem Argument um eine gültige Selektion handelt, andernfalls der tdbengine-Fehlercode.
Diese Funktion ist praktisch, wenn dem Anwender eine Selektionseingabe gestattet wird. Siehe dmk.mod.
32.5.3
SetCGILog(msg : STRING) : REAL
Mit dieser Funktion kann man die tdbengine veranlassen, einen erweiterten Log-Eintrag zu schreiben. Die genaue Diskussion erfolgt im Kapitel Debugging. Der
Rückgabewert ist immer 0.
- 321 -
Betriebssystemfunktion
32.5.4
SetFields(replacestr:STRING):REAL
Diese Funkion erlaubt das Setzen von Feldinhalten der Form: "Feld:=Ausdruck,Feld:=Ausdruck..."
Der Funktionswert ist 0, wenn alle Feldzuweisungen gültig sind, andernfalls der Fehlercode. Beispiel siehe dmk.mod.
32.5.5
CGIBuffer(mode:REAL):REAL
Mit dieser Funktion kann man die Pufferung der CGI-Ausgabe ein- und ausschalten. Nicht zu verwechseln mit CGICloseBuffer.
CGIBuffer gibt den bisherigen Pufferinhalt nicht aus (im Gegensatz zu CGICloseBuffer) und wirkt sich demnach nur auf die folgenden Schreiboperationen aus.
CGIBuffer(0) -> keine Pufferung
CGIBuffer(1) -> Ausgaben werden gepuffert.
Hinweis: Normalerweise ist die Pufferung der Ausgabe aus Performancegründen immer eingeschaltet. Der Puffer umfaßt 64 kByte.
- 322 -
Betriebssystemfunktion
32.6
Der SQL-Konflikt
Eines der Themen, die immer wieder im Brennpunkt der Diskussion standen und stehen: tdbengine ist ja ganz gut und nett, es müsse aber eine SQLDatenbank sein, denn das stehe schließlich in den Datenbank-Richtlinien der Firma (die gerade eine Datenbank-Applikation in das Internet stellen will). Wenn
man nun mit der Gegenfrage kontert, ob diese Richtlinen für alle Datenbanken gelten, auch für diejenigen vor(!) der Firewall, wird den Herren oft mulmig, denn
hier handelt es sich um Themen, von denen sie noch viel weniger verstehen als von Datenbanken oder den Spielregeln im Web. Und wenn dann noch der
Datenbank-Administrator hingezugezogen wird, so bricht das Richtlinien-Konzept des Firmenverteters ganz zusammen. Denn der Datenbank-Adminstrator
wird sich hüten, seinen ORACLE/Ingres/Informix/MS-SQL...-Server den "untrusted"-Usern im Internet zur Verfügung zu stellen. In jedem(!) Programm sind
Sicherheitslöcherenthalten, und gerade das Hacken von Datenbanken gehört zum Lieblingssport einer besonders versierten Gruppe von Internet-Surfern...
Wenn aber eine eigene zusätzlich Datenbank speziell für die Internet-Anwendung aufgebaut werden muss, wäre hier der Einsatz eines zusätzlichen
ORACLE/Ingres/Informix/MS-SQL...-Datenbank-Servers mit Panzern auf Wespen geschossen, um es einmal mit den Worten des Kriegsgottes Mars - also
martialisch - auszudrücken: übertrieben, teuer, unangemessen und letztlich an der eigentlichen Aufgabevobei.
Wir wollen an dieser Stelle das Thema (zu dem auch der Schutz personenbezogener Daten im Internet gehört) nicht vertiefen. Es wird aber neben den ganzen
technischen Aspekten eines der interessantesten Themen für die Zukunft sein, dem wir in dieser Site einen gebührenden Platz einräumen werden.
32.6.1
Die Konfigurationsdatei tdbengine.ini
Hier gibt es eine kleine Änderung. Unter der Abteilung [globals] kann ein Eintrag mit dem Namen "semadir" angelegt werden, der das Verzeichnis definiert, in
dem die Semaphoren abgelegt werden. Vorgabe ist
[globals] semadir=./
32.6.2
Zu guter Letzt
Die tdbengine meldet sich jetzt mit der internen Versionsnummer. Wenn Sie tdbengine direkt von der Konsole aus starten, erhalten Sie die übliche Meldung,
wobei jedoch nach dem Wort "tdbengine" jetzt die aktuelle Versionsnummer ausgegeben wird:
tdbengine (6.02)
- 323 -
Betriebssystemfunktion
_____________________________________
tdbengine news vom 10.5.2000
32.6.3
VAL()
Auch die Funktion VAL wurde überarbeitet. Hier gibt es nun zwei Modi:
Im strengen Modus werden ausschließlich Zahlenkonstanten bearbeitet, alles andere führt zu einem Fehler "Illegale Zahlenkonstante". Dieser Modus ist in
tdbengine voreingestellt.
Mit dem Punktbefehl .NV 0 kann der weiche Modus eingestellt werden, in dem VAL Ausdrücke wie bisher behandelt.
.NV 1
( default )
VAL("123456789")
> kein Fehler
VAL("today")
> illegale Zahlenkonstante
.NV 0
VAL("123456789")
> kein Fehler
VAL("today")
> kein Fehler
Hintergrund ist folgender: Oftmals werden Browser-Informationen (über GetQueryString oder CGIGetParam) mittels VAL in einen numerischen Wert überführt.
Das bedeutet eine extreme Sicherheitslücke!
In einen CGI-Programm steht folgende Anweisung:
...
x:=val(CGIGetParam("nummer"))
...
Ein (böser) Zeitgenosse schreibt nun in das entsprechende Formularfeld:
CgiExec('rm *')
bei Linux, bzw
CgiExec(getenv('windir')+'\system32\cmd.exe /c del *') bei WinNT
- 324 -
Betriebssystemfunktion
Das ist gemein. Aus diesem Grund haben wir in unseren Programmen auch immer eine eigene Funktion geschrieben, die VAL erst dann aufruft, wenn
sichergestellt ist, dass genau ein numerischer Wert vorliegt. Nachdem aber jede selbstgeschriebene Funktion immer langsamer ist als eine Standardfunktion,
wurde VAL nun dahingehend erweitert.
Es gibt auch noch eine Reihe kleinerer Änderungen:
So wurde die Laufvariable bei der Funktion nloop von 16 auf 32 Bit erweitert, damit hier komplette Datenbestände bearbeitet werden können.
Bei der Linuxversion wurden die bisherigen Inkompatibilitäten beseitigt, so dass nun beide Versionen identische Ergebnisse liefern. Zudem wurde diese Version
nochmals etwas optimiert.
Die Funktion SetIdent kann nun auch für Ramtexte verwendet werden.
- 325 -
Betriebssystemfunktion
33.
Neue Operatoren
Für einfache Variablen vom Typ REAL gibt es jetzt die nachgestelten Inkrement- und Dekrement-Operatoren:
VARDEF i : REAL
i++ steht für i:=i+1
i-- steht für i:=i-1
34.
Neue Funktion InitArray
Mit dieser Funktion können zur Laufzeit Felder redimensioniert werden. Damit sind dynamisch dimensionierte Felder in EASY möglich.
VARDEF vektor : REAL[]
...
high(1,vektor) // ergibt 0
initarray(vektor[10000]
high(1,vektor) // ergibt 10000
35.
Erweiterte Syntax
Bei folgenden Anweisungen wurde die strenge Zeileneinteilung aufgehoben:
IF bool THEN ... {ELSIF bool THEN ...} [ELSE ...] END
WHILE bool DO ... END
REPEAT ... UNTIL bool
RETURN [exp]
»bool« steht für einen logischen Ausdruck, »...« steht für beliebige Anweisungen
Die einzige Bedingung, die hier noch gilt, lautet: Ausdrücke dürfen nicht auf mehrere Zeilen verteilt werden.
Um Abwärtkompatibilität zu gewährleisten, gelten noch folgende Zusatzregeln:
»THEN« kann am Zeilenende weggelassen werden
»DO« kann am Zeilenende (oder wenn keine Anweisungen vorliegen) weggelassen werden
Damit können trotz der Syntaxerweiterung alle früheren Programme compiliert werden.
Beispiele für den Einsatz der erweiterten Snytax:
- 326 -
Betriebssystemfunktion
IF n=0 THEN cgiwriteln('Kein Treffer') ELSE cgiwriteln(str(n)+' Treffer') END
WHILE subst('#target#',paramstr(0)) END
IF x=y THEN i:=10; REPEAT cgiwriteln(str(i)) UNTIL i--=0 END
36.
Das System-Modul
Jetzt sind alle Standardfunktionen in dem Modul »system« zusammengefasst. Dieses Modul wird automatisch in jedes Programm importiert, und zwar vor allen
anderen Importen. Damit können alle Bezeichner aus diesem Modul umdefiniert werden. Nach einer Redefinition kann der Modulbezeichner »system« verwendet
werden, um auf den Bezeichner der Standardbibliothek zurückzugreifen.
VARDEF indname, dbname : STRING
...
indname:=system.indname(db,1)
dbname:=dbdir(db)+system.dbname(db)
Da es bisher nicht möglich war, Bezeichner aus der Standardbibliothek in eigenen Programmen zu verwenden, ist dieses Feature voll abwärtskompatibel.
- 327 -
Betriebssystemfunktion
37.
Die Markierung von Datensätzen
Die übliche Vorgehensweise, wenn es darum geht, Datensätze aus einer Tabelle auszuwählen, besteht bei EASY darin, diese Datensätze zu markieren. Jede
geöffnete Tabelle hat eine (anfangs leere) Markierungsliste, die mit folgenden Funktionen der Standardbibliothek manipuliert werden kann:
delmarks(db) löscht die gesamte Markierungsliste
setmark(db,r) fügt den Datensatz mit der physikalischen Satznummer r hinzu
delmark(db,r) entfernt den Datensatz mit der Satznummer r aus der Liste
Das folgende Programmfragment liest eine gesamte Tabelle und markiert diejenigen Datensätze, auf die eine bestimmte Bedingung zutrifft:
VARDEF db, i, fs : REAL
VARDEF PLZ_gesucht : STRING
PLZ_gesucht:='80*'
IF db:=opendb('database/adressen.dat') THEN
i:=0; fs:=filesize(db)
WHILE i++<=fs DO
readrec(db,i)
IF getfield(db,'PLZ') like PLZ_gesucht THEN setmark(db,i) END
END
END
Selbstverständlich ist das keine optimale Strategie, denn das Lesen einer gesamten Tabelle dauert recht lange. Wesentlich besser wäre beispielsweise hier eine
indizierte Suche. Da es aber nur um das Prinzip geht, wollen wir an dieser Stelle darauf verzichten.
Die Größe der Markierungsliste einer Tabelle liefert die Funktion »nmarks«:
nmarks(db) -> Anzahl der markierten Datensätze
Als nächsten Schritt wollen wir die gefundenen Datensätze nach Name und Vorname sortieren. Dafür bietet EASY die Funktion »sortmark«
sortmark(db,'Name,Vorname') ->0 alles in Ordnung, sonst Fehlercode
Selbstverständlich muss es sich bei »Name« und »Vorname« um gültige Feldbezeichner der Tabelle »db« handeln.
Mit der Funktion »revmarks« kann die aktuelle Ordnung der Markierungsliste einer Tabelle umgedreht werden
revmarks(db) : Anzahl der markierten Datensätze
- 328 -
Betriebssystemfunktion
Jetzt wollen wir die Treffer ausgeben. Dazu greifen wir auf die Funktionen »firstmark« und »nextmark« zurück.
firstmark(db) : erster Satznummer der Markierungsliste
nextmark(db,x) : nächste Satznummer der Markierungsliste bzgl. x
VARDEF x : REAL
x:=firstmark(db)
WHILE x>0 DO
cgiwrite(getfield(db,'Name')+',')
cgiwriteln(getfield(db,'Vorname')+'<br>')
x:=nextmark(db,x)
END
In manchen Fällen kommt es vor, dass man die Markierungsliste zwischenpeichern muss, weil z.B. kurzfristig eine weitere Selektion auf die Tabelle
vorgenommen werden muss. Zu diesem Zweck bietet EASY die Funktionen »getmarks« und »putmarks«. Diese Funktionen können mit unterschiedlichen
Argumenten aufgerufen werden:
VARDEF marks : MARKS
getmarks(db,marks) sichert die Markierungsliste von db in marks
putmarks(db,marks) speichert die gesicherte Markierungsliste zurück
Hinweis:putmarks überschreibt die aktuelle Markierungsliste von db.
Der Datentyp MARKS kann nur mit diesen beiden Funktionen verwendet werden. Der große Vorteil, eine Variable vom Typ MARKS einzusetzen, besteht darin,
dass
nur der wirklich benötigte Speicherplatz verwendet wird
die Sortierung innerhalb der Markierungslist erhalten bleibt
Die Markierungsliste kann aber auch auf in einem REAL-Array gespeichert werden. Hier muss der Programmierer dafür sorgen, dass das REAL-Array genügend
Elemente enthält, um die gesamte Liste speichern zu können:
VARDEF marks : REAL[]
...
initarray(marks[nmarks(db)])
getmarks(db,marks)
- 329 -
Betriebssystemfunktion
putmarks(db,marks)
Nach »getmarks(db,marks)« liegen in »marks« die physikalischen Satznummern der Markierungsliste vor. Hier eine Prozedur, in der die Markierungsiste in einer
externen Textdatei gespeichert wird:
PROCEDURE SaveMarks(db : REAL)
VARDEF text,i : REAL
VARDEF marks : REAL[]
initarray(marks[nmarks(db)])
getmarks(db,marks)
IF text:=rewrite('marks_save') THEN
nloop(i,high(1,marks)-1,writeln(text,marks[i]))
close(text)
END
ENDPROC
Auch der Einsatz von »getmarks« und »putmarks« mit einem REAL-Array ist ordnungserhaltend. Der Vorteil liegt darin, dass die Satznummern unmittelbar greifund manipulierbar vorliegen.
Schließlich gibt es noch die Möglichkeit, eine Variable vom Type TBITS zu verwenden. Um richtig zu arbeiten, muss dieses Feld so dimensioniert werden, dass
es die gesamte Tabelle abbilden kann:
VARDEF marks : TBITS[]
...
initarray(marks[filesize(db)])
getmarks(db,marks)
putmarks(db,marks)
Hier gilt nach »getmarks(db,marks)«, dass
marks[x]=1 wenn der Datensatz mit der Satznummer x in der Markierungsiste ist
marks[x]=0 andernfalls
»marks« ist somit ein Abbild der Teilmenge der Tabelle, die durch die Markierungsliste festgelegt ist. Und wie bei jeder Menge, geht hier die Ordnung verloren!
Der Vorteil der Verwendung von TBITS liegt vor allem darin, dass mit den Funktionen
bitand(b_feld1,b_feld2)
bitor(b_feld1,b_feld2)
bitandnot(b_feld1,b_feld2)
- 330 -
Betriebssystemfunktion
die wichtigsten Mengenoperationen sehr effizient durchgeführt werden können. Alle drei Funktionen liefern liefern die Cardinalität (Anzahl der Einsen) von
»b_feld_1« nach der jeweiligen Operation.
bitand: b_feld1 := b_feld1 geschnitten mit b_feld2
bitor:
b_feld1 := b_feld1 vereinigt mit b_feld2
bitandnot: b_feld1 := b_feld1 geschnitten mit dem Komliment von b_feld2 (Mengendifferenz)
Die Arbeit mit dem Datentyp TBITS ist so effizient, dass beispielsweise die Volltextsuche grundsätzlich darauf zurückgreift (»markbits«). Man wird sie immer
dann einsetzen, wenn mehrere - logisch verknüpfte - Selektionen auf eine Tabelle ausgeführt werden.
37.1
Markierungsliste auf der Platte zwischenspeichern – 17.01.2013
nFI := f_create("../database/pool/pool.mkl")
f_write(nFI,aTBits)
f_close(nFI)
Lesen
nFI := f_open(cMkl)
f_read(nFI,aTBits )
f_close(nFI)
- 331 -
Betriebssystemfunktion
38.
Grundsätzliche CGI-Frage: Was passiert, wenn der Browser die Anforderung abbricht?
Gemeinsam mit anderen CGI-Programmierern schwitzen wir gerade über einem Problem: Was passiert mit einem CGI-Programm, wenn der Klient in seinem
Browser während des Aufbaus einer (vom CGI-Prgramm gelieferten Seite) die Stop-Taste drückt (relativ selten) oder bereits einen weiteren Link aktiviert (kommt
sehr häufig vor)? In beiden Fällen wird vom Browser die aktuelle Verbindung (einseitig) aufgelöst. Was passiert mit dem gerade laufenden CGI-Programm?
Hier scheint es je nach Betriebssystem/http-Server-Kombination die unterschiedlichsten Verhaltensweisen zu geben. Ganz offensichtlich reagieren die httpServer auf dieses Problem grundsätzlich erst, wenn das CGI-Programm etwas an den Klienten senden will (solange überwachen sie den CGI-Prozess nur
bezüglich Zeitüberschreitung). Ist die Verbindung nicht mehr vorhanden (eben weil inzwischen Stop gedrückt wurde), gibt es zwei grundsätzlich verschiedene
Reaktionen:
Das CGI-Programm wird vom http-Server beendet.
Das CGI-Programm wird nicht beendet, aber die Ausgabeanweisung führt zu einem (IO-)Fehler.
Beide Fälle sind recht ungut. Der (auf den ersten Blick) etwas günstigere Fall, dass das CGI-Programm nicht beendet wird, erweist sich bei näherem Hinsehen
als noch gemeiner: Wenn das CGI-Programm nicht nach jeder Ausgabe den IO-Status testet, bekommt es den Abbruch nicht mit und meint trügerischerweise,
dass alles in Ordnung sei.
Was die ganze Angelegenheit noch gemeiner macht (jaja, es gibt immer noch eine Steigerung) ist, dass alle guten CGI-Programme (wie perl, tdbengine ...) die
Ausgabe puffern, um eine möglichst hohe Performance zu erreichen. So kann der IO-Fehler (bzw. Programmabruch) beispielsweise erst ganz am Ende des
Programms auftreten, wenn nämlich der Puffer an den Klienten übertragen wird. Dieser Zeitpunkt liegt gewöhnlicherweise noch hinter dem Ende der Prozedur
Main und entzieht sich damit komplett der Kontrolle des CGI-Programmierers.
Die (vorläufige) Lehren aus dieser Problematik lauten:
Das CGI-Programm bekommt es normalerweise nicht mit, ob und wann es vom Anwender abgebrochen wird.
Das CGI-Programm sollte nur an unkritischen Stellen CGI-Ausgaben machen (also keinesfalls innerhalb einer Transaktion)!
Das CGI-Programm sollte immer so schnell sein, dass während seiner Laufzeit kein Anwender den Stop-Knopf drücken kann.
38.1.1
Verbessertes Handling bei abgebrochenen Verbindungen
Wir haben mit einer Reihe von Linux- und CGI-Entwicklern die Frage diskutiert, was passiert, wenn während der Ausführung eines CGI-Programms der Klient die
Stop-Taste bei seinem Browser drückt oder einen anderen Link aktiviert (was auf das Gleiche hinauskommt). Wir haben nämlich bemerkt, dass in einem solchen
Fall je nach http-Server das CGI-Programm einfach (aber daür brutal) terminiert wird oder ein (spät feststellbarer) IO-Fehler ausgelöst wird.
Wir haben jetzt die tdbengine so erweitert, dass in jedem Fall ein kontrollierter Programmabbruch erfolgt, bei dem alle Dateien sauber geschlossen werden.
- 332 -
Betriebssystemfunktion
Eine wichtige Lehre aus der Diskussion für CGI-Programmierer: Keine CGI-Ausgaben während einer Transaktion, denn nur bei Ausgaben können
Abbruchsbedingungen auftreten! 38.1.2
Erweitertes Zahlenformat für Zeitangaben
Programme der TDB-Familie speichern Zeitangaben bekanntlich als Minuten ab. Die tdbengine kann zusätzlich schon seit geraumer Zeit auch Bruchteile davon
verarbeiten: Sekunden und Sekundenbruchteile. So liefert beispielsweise die Funktion NOW die Systemzeit in einer Auflösung von 1/1000 Sekunden.
Die Funktion TimeStr kann auch schon lange ein erweitertes Zeitformat ausgeben:
TIMESTR(Zeit : REAL; Nachkommastellen_bei Sekunden : REAL) : STRING
TIMESTR(NOW) -> "15:12" TIMESTR(NOW,0) -> "15:12:48"
TIMESTR(NOW,3) -> "15:12:48.429"
Jetzt kann auch der Scanner der tdbengine (=der Programmteil, der Programmtext aufbereitet) das erweiterte Zeitformat lesen:
TIMESTR(15:12:48.429) -> "15:12:48.429" 38.1.3
Neue Funktion zum Löschen eines Index
Machmal muss ein Index wieder gelöscht werden. Dafür gibt es jetzt die Funktion DELINDEX:
DELINDEX(db : Tabellenhandle; index : Indexnummer) : REAL
Rückgabewert ist 0, wenn alles in Ordnung, sonst Fehlercode.
Die ADL-Indizes können nicht gelöscht werden.
Hinweis: Die Funktion sollte mit Bedacht eingesetzt werden. Eine Tabelle kann von vielen Programmen aus genutzt werden, und vielleicht bentigt ein anderes
Programm gerade den Index, den Sie eben gelöscht haben... 38.1.4
Umbenennung der Funktion PRIMFILE in PRIMTABE
Die Funktion PRIMFILE steht jetzt auch unter dem Namen PRIMTABLE zur Verfügung. Sie sollten den neuen Namen verwenden, da er irgendwann in der
Zukunft den alten Funktionsnamen (der semantisch nicht stimmt) ersetzen wird.
- 333 -
Betriebssystemfunktion
38.2
DiewesentlichenNeuerungenderVersion6.25:
Version für FreeBSD 4.x
Aufhebung der 64-KByte-Grenze für EASY-Programme
Neue Funktion: DBRights liefert die aktuellen Rechte an einer geöffneten Tabelle
Erweiterung des feldbezogenen Volltext-Index
Kleinere Verbesserungen
38.2.1
Version für FreeBSD 4.x
Neben Linux erfreut sich FreeBSD immer größerer Beliebtheit - vor allem beim Einsatz auf Web-Servern. Zwar gibt es hier die Möglichkeit, Linux-Programme mit
Hilfe eines Emulators zum Laufen zu bringen. Höhere Performance und bessere Systemanbindung bringen freilich Programme, die direkt für dieses Unix-Derivat
kompiliert wurden.
Die tdbengine für FreeBSD hat den gleichen Funktionsumfang wie die Linux-Version. Da allerdings der Compiler, mit dem die Übersetzung erfolgte, noch im
Beta-Stadium ist, gilt das Gleiche für die tdbengine für FreeBSD.
- 334 -
Betriebssystemfunktion
38.2.2
Aufhebung der 64-KByte-Grenze für EASY-Programme
Der p-Code, den die tdbengine aus einem EASY-Programm compiliert, ist frei verschiebbar, wobei Sprünge (bei Schleifen und Bedinungen) durch einen 16-BitOffset angegeben werden. Der Startpunkt einer Prozedur/Funktion wurde bislang ebenfalls durch einen 16-Bit-Wert angegeben. Dieser Wert wurde nun auf 32
Bit erweitert, so dass jetzt ein adressierbarer Raum von 4 GByte zur Verfügung steht. Der 16-Bit-Offset wurde belassen, weil größere relative Sprünge in der
Praxis nicht vorkommen.
Etwas vereinfacht bedeutet dies nun:
Der Code einer Prozedur oder Funktion kann bis zu 64 KByte umfassen.
Der Code eines Programms kann bis zu 4 GByte groß werden.
Durch die erweiterte Addresierung musste der Programm-Vorspann verändert werden, wodurch auch eine Änderung der PRG-internen Versionsangabe
notwendig wurde. Weil aber auf vielen Servern unter Umständen viele Proramme aktiv sein können, hat die neue tdbengine einen Kompatibiltäts-Lader, der
Programme aus vorherigen Versionen beim Laden an die aktuelle Version anpasst. Das bedeutet nun:
Mit früheren Versionen der tdbengine erzeugte Programme werden von der neuen tdbengine ausgeführt.
Mit der neuen Version erzeugte Programme werden von früheren Versionen der tdbengine nicht mehr ausgeführt. Hier erfolgt die Fehlermeldung "Falsche
Programmversion".
Eine kleine Einschränkung gibt es freilich: Wenn ein Programm auf die Systemvariable "Fehler" zugreift, muss es mit der neuen Version übersetzt werden. Also
in jedem Fall die Datei tdbengine/bin/error.log beobachten!
Dazu noch ein kleiner Tipp: Verzichten Sie grundsätzlich darauf, die Systemvariablen einzusetzen, und verwendet Sie stattdessen die entsprechenden
Funktionen: also anstelle von der Variablen "Fehler" die Funktion TDB_LastError verwenden!
Hinweis: Die Aufhebung der 64-Kbyte-Grenze sollte aber keineswegs dazu führen, das Sie jetzt PRGs im MByte-Bereich erzeugen. Gerade bei CGIProgrammen spielt die Ladezeit eine zwar kleine, aber doch messbare Rolle. Zu sehr aufgeblähte Programme könnten sonst der exzellente Performance der
tdbengine abträglich sein.
Vorschau: Schon bald wird die tdbengine über einen intelligenen Linker verfügen. Zusammen mit der erweiterten Adressierung wird dann der Einsatz von großen
EASY-Bibliotheken möglich sein.
38.2.3
Neue Funktion: DBRights
Das Öffnen und Schliessen von Tabellen gehört zu den aufwendigen Operationen. Deshalb wird eine Tabelle in einem Programm üblicherweise nur einmal
geöffnet, und der Tabellenhandle dann als Parameter an die jeweiligen Prozeduren übergeben.
- 335 -
Betriebssystemfunktion
Wenn eine Prozedur nun schreibend auf die eine Tabelle zugreifen will, kann sie das nur, wenn die Tabelle im entsprechenen Modus geöffnet wurde. Um das
herauszubekommen gibt es nun die Funktion DBRights:
DBRIGHTS(db : REAL) : REAL
Parameter
db : Tabellenhandle von OPENDB
Rückgabewert
Rechte gemäß OPENDB
- 336 -
Betriebssystemfunktion
38.3
Verbesserte Syntax und initialisierte Variablen
Nach wie vor besteht das dringende Bedürfnis, die Syntax von EASY einfacher und transparenter und leistungsfähiger zu machen.
VARDEF wird durch VAR ersetzt.
veraltet:
VARDEF x,y,z : STRING
VAR a = 'Hans'
jetzt:
VAR x,y,z : STRING
VAR a : STRING = 'Hans'
ebenfalls gültig:
VAR d,e,f : INTEGER = 1 // hier werden alle Variablen entsprechend initialisiert
Hierbei handelt sich um eine (eigentlich folgenschwere) Erweiterung. Denn initialisierte Variablen sind nunmehr auch global möglich. Die Initialisierung erfolgt
unmittelbar nach dem Laden des Moduls. Wird ein Modul mittels "USES" eingebunden, so werden auch dessen globale Variable initialisert.
Beachten Sie bitte auch, dass die tdbengine (wenn nichts anderes angegeben ist) Variablen (auch Felder) immer initialisert:
Typ
Initialierunswert
REAL
0.0
STRING
''
CHAR
ASC(0)
BYTE
0
INTEGER
0
MARKS
<leer>
TBITS
0
Allgemein lautet die Syntax:
- 337 -
Betriebssystemfunktion
Variablendeklaration::="VAR" Bezeichnerliste ":" Typ ["=" Ausdruck].
Der Ausdruck wird nicht zur Compilierzeit, sondern zur Laufzeit(!) berechnet und zugewiesen. Das bedeutet wiederum, dass hier auch selbstdefinierte
Funktionen verwendet werden dürfen.
Dazu ein
MODULE counter; // location: lib/counter.mod
PROCEDURE Init : REAL; VAR result, ec_before : INTEGER;
ec_before:=getpara('ec');setpara('ec1');
result:=opendb("database/counter/counter.dat","",0,15);
setpara('ec'+str(ec_before));
IFresult=0THEN
cgiwriteln('content-type:text/html');
cgiwriteln('');
cgiwriteln('database-error:'+TDB_ErrorStr(TDB_LastError));
HALT
ELSE
RETURNresult;
END
ENDPROC;
VAR db : INTEGER = Init;
Greift nun ein anderes Programm auf dieses Modul zu, so steht bereits nach
USES counter.mod;
mit counter.db ein gültiger (und zum Schreiben geöffneter) Tabellenhandle (der Tabelle database/counter/counter.dat) zur Verfügung.
Internes
Intern geht die tdbengine bei der Initialisierung von Variablen folgendermaßen vor:
Handelt es sich um lokale Variablen (die innerhalb einer Prozedure deklariert werden), so wird der Initialiserungscode genau an der Stelle aufgenommen, an der
auch die Deklaration steht.
- 338 -
Betriebssystemfunktion
PROCEDURE xyz;
TueDiesUndDas
VAR a : STRING = 'hallo'
TueJenes
ENDPROC
Daraus macht die tdbengine:
PROCEDURE xyz;
VAR a : STRING
TueDiesUndDas
a:='hallo'
TueJenes
ENDPROC
Bei einer globalen Variable wird indessen eine eigene Prozedur "_init" angelegt, in der sämtliche Initialisierungen für alle Module des Programms eingetragen
werden. Diese Prozedur wird beim Start des Programms unmittelbar nach dem Laden (und damit vor "Main") ausgeführt.
Hinweis:
Aus Kompatibilitätsgründen wird die Syntax
VARDEF a,x,c : TYP
VAR x = exp
weiterhin unterstützt. Allerdings sollten Sie dazu übergehen, nur noch die neue Syntax zu verwenden.
Kommentare
Mit den (aus C bekannten) Zeichen /* ... */ lassen sich nun (nahezu) beliebige Textteile eines Programms als Kommentar kennzeichnen:
Module Kommentar_Test;
/* Modul zum Testen von Kommentaren,
Die über mehrere Zeilen gehen,
Wie dieser Kommentar hier*/
/* Aber auch Kommentare innerhalb einer Zeile sind möglich: */
- 339 -
Betriebssystemfunktion
VAR i : INTEGER;
VAR max : INTEGER = 10000;
WHILE i++<=max /* AND NOT ready */ DO
// Hier ist ein anderer Kommentar bis zum Zeilenende
END
Vorläufig gilt noch folgende Einschränkung: Kommentare dürfen nicht in Zeilen beginnen, die mit VAR oder PROCEDURE beginnen:
PROCEDURE xyz(i : INTEGER /*; s : STRING */ ) : INTEGER; // ist nicht zulässig
38.4
Neue Datentypen
EASY kennt nun folgende neuen Datentypen für Variablen:
CHAR
BYTE
INTEGER
CHAR speichert genau ein Zeichen, ist ansonsten kompatibel zu STRING. Es gilt
VAR x : CHAR VAR y : STRING
x:='A' -> ASC(x) = 65 x:='' -> ASC(x) = 0 // Der Leerstring wird auf das Zeichen CHR(0) abgebildet
x:='ABC' -> ASC(x) = 65 // es wird nur das erste Zeichen übernommen
Besonders interessant sind Felder vom Typ CHAR. Diese werden in der nächsten Version weitgehend kompatibel zum Typ STRING sein:
VAR x : CHAR[1000]
x:='Hallo Welt' -> x[0]:='H'; x[1]:='a'; x[2]:='l'; ...
BYTE speichert eine natürliche Zahl von 0 bis 255, ist ansonsten kompatibel zu REAL
- 340 -
Betriebssystemfunktion
Wiederum sind hier Felder vom Typ BYTE interessant:
VAR b : BYTE[10000]
Die Datentypen CHAR und BYTE (und vor allem Felder davon) werden erst in der nächsten Version wirklich nützlich, da sie dann als Ein- oder Ausgabepuffer für
Datenströme (streams) Verwendung finden. Und darunter versteht die tdbengine dann beliebige Dateien und (Unix-)Sockets.
Bereits jetzt mit Vorteil einsetzbar ist der Datentyp INTEGER. Ein Variable von diesem Typ speichert eine ganze Zahl von -2147483648 bis +2147483647. Er
sollte immer dann eingesetzt werden, wenn keine Kommstellen benötigt werden und der Zahlenbereich für die Variable ausreicht. Da alle Tabellenfunktionen
intern mit solchen Größen arbeitet, ist INTEGER hier beispielsweise der Typ der Wahl:
VAR db : INTEGER;
..
db:=OpenDB('database/adressen/adress.dat');
INTEGER-Variablen können überall dort verwendet werden, wo auch REAL-Variablen erlaubt sind. Das gilt auch für Felder dieses Typs:
VAR marks : INTEGER[];
VAR db, i, fs, n : INTEGER;
...
IF db:=OpenDB('database/adressen/adress.dat') THEN
fs:=FileSize(db);
InitArray(marks[fs]);
nLoop(i,fs-1,SEL(GetRField(db,'Status')<>3 OR marks[n]:=i+1, n++))
// IF $Status=3 THEN marks[n]:=i+1; n++ END;
PutMarks(db,marks)
END
Auch die Funktion InArray ist mit (eindimensionalen) INTEGER-Feldern erlaubt
38.5
NeueunderweiterteFunktionen
Grundsätzliche Erweiterung bei allen Index-Funktionen: Zu diesem Thema gibt es einen eigenen Artikel im Dokumentations-Menü. An dieser Stelle deshalb nur
der Hinweis, dass alle Indexfunktionen jetzt wahlweise den Index-Namen oder die Index-Nummer verwenden können.
- 341 -
Betriebssystemfunktion
38.5.1
MarkDoubles(db:INTEGER;index:INTEGER|STRING):INTEGER
db: Tabellenhandle von OpenDB
index: Indexnummer oder Indexname
Rückgabewert: Anzahl der doppelten Datensätze (bzgl. der Indexinformation)
Die Funktion funktioniert folgendermaßen: Die Tabelle wird in Reihenfolge des angegebenen Index untersucht. Liefert dabei ein Datensatz die gleiche
Indexinformation wie sein Vorgänger, so wird er markiert.
Die Funktion ist sehr schnell, da nur Einträge aus der Indexdatei, nicht aber aus der Tabelle gelesen werden müssen. 38.5.2
DelMarkedRecords(db:INTEGER):INTEGER
db : Tabellenhandle von OpenDB
Rückgabewert: Anzahl der gelöschten Datensätze
Mit dieser Funktion können recht schnell alle markierten Sätze einer Tabelle gelöscht werden.
Tritt beim Löschen ein Fehler auf, so wird ein Laufzeitfehler ausgelöst. 38.5.2.1 Reset, Rewrite und TAppend
Reset, Rewrite und TAppend dienen zum Öffnen von (internen und externen) Textdateien. Bisher wurden solche Dateien immer im ASCII-Zeichensatz gelesen
und geschrieben. Bei ANSI-Dateien musste eine Stringkonvertierung mit OemToAnsi und AnsiToOem erfolgen.
Jetzt kann als zusätzlicher Parameter angegeben werden, ob es sich um eine ASCII- oder ANSI-Datei handelt.
0 : ANSI
1 . ASCII (Vorgabe)
- 342 -
Betriebssystemfunktion
VAR t, i, db, x : INTEGER;
...
t:=rewrite('export.txt',0); // ANSI
readrec(db,x);
nloop(i,maxlabel(db)-1,writeln(t,getfield(db,i+1)))
...
38.5.3
read(f:INTEGER[n:INTEGER|d:CHAR]):STRING
Mit dem (optionalen) Parameter d ist es jetzt möglich, bis zu einem definierten Zeichen aus der Textdatei zu lesen (max. 255 Zeichen). Das Trennzeichen d
selbst wird nicht zurückgeliefert:
read(t)
=read(t,1)
liest das nächste Zeichen aus der Textdatei t
read(t,n)
1<=n<=25 5
liest die nächsten n Zeichen aus der Textdatei
read(t,d)
d : CHAR
liest (maximal 255) Zeichen aus der Textdatei, bis entweder d gelesen oder das Ende der Datei erreicht wird
38.5.4
Verbesserungen
USES sucht nun auch im Verzeichnis des aufrufenden Programms.
GetIdent kann nun plattformübergreifend eingesetzt werden (ZeilenendemitCR/LFoderLF).
SetPara('nb1') kann einen Performancegewinn von bis zu 25% bringen.
38.6
EinfacheSocket-Funktionen
Die folgenden vier Funktionen sind in allen Versionen der tdbengine verfügbar. Mit ihnen ist es möglich, Klients aus allen Bereichen zu schreiben.
OpenSock(adr : STRING) : INTEGER // öffnet einen TCP-Stream-Socket
adr ist ein String der Form: IP:Port wie '192.168.1.100:80' oder auch 'mail.tdb-engine.de:25'
Rückgabewert ist ein Socket-Handle (1 bis 32)
CloseSock(s : INTEGER) : INTEGER // schließt einen offenen Socket
s : Handle von OpenSock - 343 -
Betriebssystemfunktion
GetSock(s : INTEGER; VAR p : CHAR[]; maxchars : INTEGER) : INTEGER;
GetSock(s : INTEGER; VAR p : BYTE[]; maxbyte : INTEGER) : INTEGER;
Überträgt aus dem Socket maxchars Zeichen bzw. maxbyte Bytes in die Array-Variable p.
Rückgabewert: Anzahl der wirklich übertragenen Zeichen/Bytes. PutSock(s : INTEGER; VAR p : CHAR[]; maxchars : INTEGER) : INTEGER;
PutSock(s : INTEGER; VAR p : BYTE[]; maxbyte : INTEGER) : INTEGER;
Überträgt zu dem Socket maxchars Zeichen bzw. maxbyte Bytes aus der Array-Variablen p.
Rückgabewert: Anzahl der wirklich übertragenen Zeichen/Bytes.
Kleines Programmbeispiel (bitte nicht unverändert ausprobieren, ich krieg schon so genügend Mails)
PROCEDURE send_str(s : INTEGER; l : STRING);
VAR p : CHAR[1000];
p[0]:=l+^M+^J; PutSock(s,p,length(l)+2)
cgiwriteln('> '+l)
ENDPROC
PROCEDURE recv_str(s : INTEGER) : STRING;
VAR p : CHAR[1000]; VAR i : INTEGER; VAR res : STRING;
nloop(i,GetSock(s,p,1000)-1,res:=res+p[i]);
RETURN res ENDPROC
- 344 -
Betriebssystemfunktion
PROCEDURE Main;
VAR p : CHAR[1000]; VAR i : INTEGER;
cgiclosebuffer;
cgiwriteln('content-type: text/plain');
cgiwriteln('');
IF i:=OpenSock('mail.tdb-engine.de:25') THEN
cgiwriteln(recv_str(i));
send_str(i,'HELO mail.tdb.de');
cgiwriteln(recv_str(i));
send_str(i,'MAIL FROM:[email protected]');
cgiwriteln(recv_str(i));
send_str(i,'RCPT TO:[email protected]');
cgiwriteln(recv_str(i));
send_str(i,'DATA')
cgiwriteln(recv_str(i));
send_str(i,'Das ist ein kleiner Test.');
send_str(i,'Und das ist die zweite Zeile');
send_str(i,'.');
cgiwriteln(recv_str(i));
send_str(i,'QUIT');
cgiwriteln(recv_str(i));
CloseSock(i)
ELSE
cgiwriteln('done.')
END ENDPROC
- 345 -
Betriebssystemfunktion
Und so ist die Ausgabe des Programms:
220 www.tdb-engine.de ESMTP Sendmail 8.10.2/8.10.2/SuSE Linux ...
> HELO mail.tdb.de
250 www.tdb-engine.de Hello [62.208.108.251], pleased to meet you
> MAIL FROM:[email protected]
250 2.1.0 [email protected]... Sender ok
> RCPT TO:[email protected]
250 2.1.5 [email protected]... Recipient ok
> DATA
354 Enter mail, end with "." on a line by itself
> Das ist ein kleiner Test.
> Und das ist die zweite Zeile
>.
250 2.0.0 f9GE7Kj09453 Message accepted for delivery
> QUIT
221 2.0.0 www.tdb-engine.de closing connection
Alle Socket-Funktionen führen im Fehlerfall zu einem (behandelbaren) Laufzeitfehler.
GetSock und PutSock liefern im Fehlerfall negative Ergebnisse,OpenSock den Wert 0.
- 346 -
Betriebssystemfunktion
38.7
tdbengine als Unix-Scriptsprache
Und noch eine schöne Neuerung (nur unter Linux/FreeBSD praktikabel):
EASY-Programme können unter Linux/FreeBSD jetzt (auch) als "normale" Scripten aufgefasst werden.
Dazu wird, wie bei der Shell, Perl und anderen Scriptsprachen, in der ersten Zeile der Interpreter angegeben:
#!/home/tdbengine/bin/tdbengine
Oder, wenn eine Kopie der tdbengine in /usr/local/bin liegt:
#!/usr/local/bin/tdbengine
Dann wird das EASY-Script ausführbar gemacht und kann anschließend einfach wie jedes andere Programm auch gestartet werden. Dadurch wird die
tdbengine mit dem Script als Parameter gestartet. Anhand der ersten Zeile erkennt nun die tdbengine, dass es sich dabei um ein direkt auszuführendes Script
handelt, übersetzt dieses und führt schließlich die Prozedur "Main" aus.
#!/home/tdbengine/bin/tdbengine
VARspooldir:STRING='/var/spool/outgoing_mails'
PROCEDUREclear_spooler
VARl:STRING=firstdir(spooldir+'/*.msg','')
VARfn,s_to,s_from,host:STRING
VARp,q:INTEGER
host:=getenv('SERVER_NAME')
WHILE l DO
fn:=rtrim(l[1,63])
loadtemplate(spooldir+'/'+fn)
IF p:=ramtext_find('ramtext','To:') THEN
q:=ramtext_find('ramtext',^J,p+1)
IF q-p<255 THEN
s_to:=ramtext_part('ramtext',p+4,q-p-4)
END
END
- 347 -
Betriebssystemfunktion
IF p:=ramtext_find('ramtext','From:') THEN
q:=ramtext_find('ramtext',^J,p+1)
IF q-p<255 THEN
s_from:=ramtext_part('ramtext',p+6,q-p-6)
END
END
IF s_to,s_from THEN
CGIExec('sendmail-v-pSMTP:'+host+'-f'+s_from+''+s_to+'<'+spooldir+'/'+fn+'>>'+spooldir+'/mail.log')
delfile('mails/'+fn)
END
l:=nextdir
END
ENDPROC
PROCEDUREMain
EndSema
REPEAT
pause(1000)
clear_spooler
UNTIL 0
ENDPROC
Speichern unter /usr/local/sbin/mailer Ausführbar machen: chmod a+x /usr/local/sbin/mailer
Dann einmal starten:
sudo -b /usr/local/sbin/mailer
...und schon haben wir einen Mail-Versender, der alle zehn Sekunden Mails aus dem Spoolverzeichnis verschickt und dabei ein sauberes Log-File schreibt.
Freilich könnte man das auch mit
/home/tdbengine/bin/tdbengine mailer.prg
machen, aber
- 348 -
Betriebssystemfunktion
entfällt hier der Compilierungsvorgang (er erfolgt sozusagen on the fly)
bleibt hier absichtlich der Script-Quelltext das tragende Element und ist somit permanent verfüg- und damit wartbar erhöht die Anlehnung an der Standard (hoffentlich) die Akzeptanz der tdbengine (Scriptsprache mit eingebauter Datenbank)
38.7.1
Weitere Verbesserungen
LoadTemplate mit http-Adressen funktioniert jetzt auch in der Windows-Version.
IF LoadTemplate('http://www.irgendeinedomain.de/templates/irgendeintamplate.html')=0 THEN ... // Template ist da
ELSE ... // Fehlermeldung
END
...
Diese Funktion ist nicht zu unterschätzen, erlaubt sie doch nicht nur (wie bisher) die funktionale, sondern (nunmehr) auch die räumliche Trennung von
(CGI-)Programmierung und (HTML-)Design
38.7.2
Hinweise zur Fehlersuche
Die tdbengine ist wieder einmal ein bisschen strenger geworden. Diesmal geht um das Lesen und Schreiben von Ramtexten:
reset(Ramtext) liefert einen Laufzeitfehler (1), wenn der Ramtext bereits zum Schreiben geöffnet ist. rewrite(Ramtext) liefert einen Laufzeitfehler (1), wenn der Ramtext bereits zum Lesen geöffnet ist. 38.7.3
Neue Funktionen zum ausprobieren (nurLinux-Version)
Zwei ganz neue Funktionen bietet die Linux-Version: TimeOut und Server. Beide bieten in Verbindung mit den bisher bereits eingebauten Socket-Funktionen
neue Funktionalitäten.
- 349 -
Betriebssystemfunktion
38.7.3.1 Timeout(sec:INTEGER):INTEGER
Diese Funktion ruft die Linux-Funktion Alarm mit dem übergeben Argument auf. Der Linux-Kernel sendet dann nach Ablauf der angebenen Zeit (in Sekunden) ein
Signal (SIGALRM) an den aufrufenden Prozess. Die tdbengine verbeitet dieses Signal, indem sie diejenige Prozedur, die zum Zeitpunkt des Signals gerade
abgearbeitet wird, mit RETURN 0 (bzw. RETURN '' bei einer STRING-Funktion) verlässt.
Wird vorher, (also bevor das Signal gesendet wird) die Funktion TimeOut mit dem Parameter 0 aufgerufen, so wird kein Signal gesendet.
Mit diesem Mechanismus kann man sich recht einfach TimeOuts basteln. Hierzu ein kleines
PROCEDURE Eingabe : INTEGER
VAR id : STRING
Timeout(10)
writeln(0,'Bitte Ihre User-Kennung: '
id:=readln(0)
TimeOut(0)
RETURN 1
ENDPROC
PROCEDURE Main
IF Eingabe
THEN writeln(0,'Ihre Eingabe wird bearbeitet...')
ELSE writeln(0,'Abbruch wegen Zeitüberschreitung')
END
ENDPROC
Wenn man das kleine Programm an der Konsole startet, so kann man innerhalb von 10 Sekunden seine ID angeben, und das Programm macht weiter, als wenn
nicht gewesen wäre. Andernfalls erhalten Sie Meldung 'Abbruch wegen Zeitüberschreitung'.
Für Bastler vielleicht noch interessanter ist die Funktion Server:
- 350 -
Betriebssystemfunktion
38.7.3.2 Server(port:INTEGER;proc:Prozedur;maxconnections:INTEGER):INTEGER
Damit installieren Sie einen Multi-Prozess-Server, der auf dem angegebenen Port wartet und Verbindungen vom Typ Internet-Socket annimmt. Bei erfolgreicher
Verbindung wird ein Kind-Prozess gestartet , der die Verbindung übernimmt. Der Vaterprozess steht somit sofort wieder bereit, weitere Verbindungen
anzunehmen. Zusätzlich wird das Pseudo-Texthandle 128 zur Verfügung gestellt, über das Sie die Kommunikation mit dem Klienten abwickeln können (in dieses
Handle können Sie schreiben und lesen). Schließlich wird die Prozedur proc ausgeführt. Wird diese beendet, so wird auch die Verbindung zum Klienten
geschlossen und der Kind-Prozess beendet. Dieser bleibt allerdings zunächst als Zombie im Speicher. Allerdings räumt die tdbengine bei jedem neuen
Verbindungsaufbau eventuel vorhandene Zombies auf.
Normalerweise terminiert die Funktion Server nicht. Sie läuft in einer Endlosschleife (das machen Dämonen nun mal so). Falls Sie doch terminiert, liefert das
Funktionsergebnis den Fehlercode des Betriebssystems (wenn beispielsweise der Prot schon von einem anderen Dämonen überwacht wird.
Hier ein kleines Beispiel
PROCEDURE try
VAR s : STRING
writeln(128,'Hello '+getenv('REMOTE_ADDR')+', here is the server...')
REPEAT
s:=readln(128)
writeln(128,s)
UNTIL s='quit'
ENDPROC
PROCEDURE Main
cgiclosebuffer
server(3444,try,1)
ENDPROC
Wie Sie vielleicht daraus ersehen, setzt die tdbengine beim Aufbau einer Verbindung die Environment-Variable 'REMOTE_ADDR', in der die IP-Nummer des
Klienten abgelegt wird.
Starten Sie dieses Programm auf den Konsole. Mit Telnet können Sie den "Server" testen:
telnet 192.168.1.2 3444
Trying 192.168.1.2...
Connected to linux_engine
Escape character is '^]'.
- 351 -
Betriebssystemfunktion
Hello 192.168.1.3, here is the server...
Hello
Hello
quit
quit
Connection closed by foreign host.
Dieser kleine Demoserver schickt alle Zeichenketten zurück an den Klienten, bis dieser "quit" eingibt.
Wichtig: Jede Verbindung führt zu einem eigenen, vollkommen insolierten Prozess. Jeder Prozess hat seine eigenen Variablen, die Synchronisation muss über
geeignete Semaphoren sichergestellt werden.
38.7.4
Erweiterungen von anderen Funktionen
Subst wurde um den Modus 128 erweitert. In diesem Fall wird die Groß-/Kleinschreibung beim Target (der zu ersetzenden Zeichenfolge) ignoriert. Man sollte
diesen Modus jedoch nur wählen, wenn er wirklich erforderlich ist, da die zusätzlich benötigte Rechenleistung doch enorm ist.
Den gleichen Modus mit der gleichen Wirkung gibt es jetzt auch für Ramtext_Find.
TAppend funktioniert nun auch für Ramtexte.
38.7.5
Korrekturen
Zum Export einer Tabelle in das DBF-Format steht schon seit langem die bisher undokumentierte Funktion ToDBF zur Verfügung:
38.7.5.1 ToDBF(dat:INTEGER;fn_dbf:STRING;lastfield,ansi:INTEGER)
dat : Handle von OpenDB
fn_dbf : Name der DBF-Datei
lastfield : es werden die Felder von 1 bis lastfield exportiert (0 = alle Felder)
ansi : 0 = ASCII in DBF, 1 = ANSI in DBF (für FoxPro)
Es wird eine Tabelle im DBase-4-Format erzeugt, mit der kein anderes Programm Probleme haben sollte.
Die Funktion kommt jetzt auch mit den erweiterten Datentypen der tdbengine (NUMBER,8 bzw. NUMBER,4) und langen Memos zurecht.
- 352 -
Betriebssystemfunktion
38.7.6
NeueFunktionen
38.7.6.1 CGITestParam(parameter:STRING):0|1
CGITestParam liefert eine 1, wenn ein CGI-Parameter entsprechenden Namens vom Browser (mit der Methode POST) übertragen wurde, andernfalls 0. Es gilt
jedoch zu beachten, dass bei vielen HTML-Input-Konstrukten (beispielsweise Checkboxen oder Radiobuttons) überhaupt nichts übertragen wird, wenn der
Anwender solche Elemente nicht auswählt.
38.7.6.2 ExecProg(FileName:STRING):INTEGER
Bei dieser Funktion handelt es sich eigentlich um eine alte TDB-Funktion, die jetzt in der tdbengine reaktiviert wurde. Der Parameter FileName verweist auf eine
Textdatei, die EASY-Code enthält. Dieser Code wird zunächst kompiliert und dann sofort ausgeführt. Gerade in Verbindung mit Ramtexten ergeben sich sehr
schöne Möglichkeiten: Man setzt sich komplexe Datenbankabfragen in einem Ramtext zusammen und führt diesen dann aus.
Beim Einsatz von ExecProg ist zweierlei zu beachten: Innerhalb des auszuführenden Codes kann man zwar auf alle Variablen des aufrufenden Programmes
zugreifen, nicht aber auf die Prozeduren und Funktionen. Und im Code verwendete Variablen werden durch den Compiler verändert, müssen also im Code
explizit auf den aktuellen Wert gesetzt werden.
- 353 -
Betriebssystemfunktion
38.8
Programminterne Kommunikation über Environment-Variablen
Es gibt nun zusätzlich zum Environment, das vom Betriebssystem zur Verfügung gestellt wird, zwei weitere Variablen, auf die innerhalb eines EASY-Programms
zugegriffen werden kann:
TDB_VERSION
Die tdbengine liefert hier String der Form "6.2.9"
TDB_OS
Da gibt es derzeit zwei Antworten:
"unix" für FreeBSD und Linux
"win32" für Windows
Mit einer (bislang undokumentierten) Spezialform der Funktion GetEnv) kann man auch Variablen für das interne Environment der tdbengine setzen:
GetEnv('set:Varibale=Wert')
GetEnv('set:MEIN_NAME=Hans Mustermann')
In der Folge liefert GetEnv('MEIN_NAME') den String 'Hans Mustermann'. Diese Variable dann solange gültig, bis sie entweder umdefiniert oder aber die
tdbengine beendet wird.
Zwei selbstdefinierte Environment-Variablen werden von der tdbengine selbst ausgewertet:
HTTP_PROXY
Der Inhalt dieser Variablen wird verwendet, wenn ein Template von einem entfernten Rechner via http geladen wird, also bei
LoadTemplate('http://...')
Ist die Variable HTTP_PROXY auf die IP-Adresse (oder bekannten Rechnernamen) gesetzt, so wird die Anfrage an diesen geleitet. Der Zugriff eines WebServers auf des Web über einen Proxy ist ein gängiges Verfahren, Sicherheitsrisiken zu minimieren.
- 354 -
Betriebssystemfunktion
TDB_SUBST
Diese Variable wird dann ausgewertet, wenn die tdbengine als Server läuft, also die Funktion SERVER aktiv ist. In diesem Fall wird die Funktion
CGIWRITETEMPLATE so erweitert, dass vor der Ausgabe des Templates sämtliche Ersetzungen durchgeführt werden, die in der Datei stehen, auf die
TDB_SUBST verweist. Diese Datei ist zeilenweise so aufgebaut:
Target;Ersetzung
Statt langer Erklärungen hier ein
Angenommen wir haben folgendes Template:
<html>
<head>
<base href="http://www.tdb-engine.de/">
</head>
<body>
<img src="http://www-tdb-engine.de/pics/einbild.jpg">
<h3>Herzlich Willkommen</h3>
<a href="/scripts/anfang.prg">Zur Einleitung</a><br>
<a href="/scripts/ende.prg">Zum Ende</a><hr>
</body>
</html>
Im aktuellen Verzeichnis gibt es eine Text-Datei 'local' mit folgendem Inhalt:
www.tdb-engine.de;localhost:3444
/scripts/;/cgi-tdb/local/
/anfang.prg";anfang.prg?query=start"
- 355 -
Betriebssystemfunktion
NachGetEnv ('set:HTTP_SUBST=local') gibt die tdbengine das obige Template (mit CGIWriteTemplate) so aus:
<html>
<head>
<base href="http://localhost:3444/">
</head>
<body>
<img src="http://localhost:3444/pics/einbild.jpg">
<h3>Herzlich Willkommen</h3>
<a href="/cgi-tdb/local/anfang.prg?query=start">Zur Einleitung</a><br>
<a href="/cgi-tdb/local/ende.prg">Zum Ende</a><hr>
</body>
</html>
Alles klar? Zugegeben, es ist kompliziert. Aber die Möglichkeit, die sich aus diesem Feature erschließt, ist einfach überwältigend: Sie können einen bestehenden
Internetauftritt (mit dynamischen Inhalten, sonst wäre es ja langweilig) mit ganz wenigen Handgriffen so auf eine CD brennen, dass dieser ohne eine Änderung
an den Programmen oder HTML-Seiten sofort von dieser CD läuft. Machen Sie das mal mit irgendeinem anderen Programm (PHP/MySQL, Perl ... )
38.8.1.1 Cookies
Da die Auswertung von Environment-Variablen nicht jedermanns Sache ist, wird die Auswertung der Variablen HTTP_COOKIE jetzt auch von der tdbengine
übernommen. Diese stellt die einzelnen Cookies über die Funktion CGIGetParam zur Verfügung:
CGIGetParam("cookie.name") liefert den Wert des Cookies mit dem Bezeichner "name".
- 356 -
Betriebssystemfunktion
39.
Tdbengine.ini
Jedesmal, wenn tdbengine gestartet wird, liest das Programm zunächst die Datei tdbengine.ini. Über diese Datei wird das Verhalten von tdbengine weitgehend
definiert.
Die Konfigurationsdatei wird in folgenden Verzeichnissen gesucht:



Im Verzeichnis, in dem sich das auszuführende Programm befindet
Im Verzeichnis, in dem sich die (ausführbare) tdbengine(.exe) befindet
Im Verzeichnis /etc/tdbengine
(unter Linux)
tdbengine.ini wird im Format von Windows-Konfigurationsdateien gestaltet, die in einzelnen Abteilungen Zeilen der Form "Schlüssel=Wert" enthält.
Die Abteilungen werden durch Begriffe in eckigen Klammern definiert.
Der erste Teil unter der Abteilung [globals] beinhaltet die allgemeinen Einstellungen.
Folgende Einträge sind möglich (bitte beachten Sie Groß/Kleinschreibung):
Stopcgi
location
Timeout
[globals]
X
X
X
Overrun
Logcgi
Log
Cgibuf
Libpath
X
X
X
X
X
setcgilog
cdmode
Sema
Semadir
Nobreak
X
X
X
index_pagesize=$ffff
X
[programmspezifisch]
Erklärung
Ausführung eines CGI-Programmes zulassen oder blockieren
Weiterleitung auf HTML-Seite
timeout in Millisekunden, bei Abbruch wird die unter <overrun> angegebene Seite ausgeführt
Der TimeOut wird nur berücksichtigt, wenn die globale Semaphore CGI verwendet wird.
Pfad zu den Verzeichnissen, in denen mit uses eingebundene Module beim Compilieren gesucht
werden
X
nobreak=0 tdbengine prüft zyklisch ob die Datei "abort" sich im tdbengine-Verzeichnis befindet.
Findet sie eine solche Datei, so bricht sie augenblicklich die Ausführung der PRG ab und versucht
die Abort-Datei zu löschen.
Bei größeren Tabellen benötigt muss die index_pagesize=$ffff gesetzt werden, andernfalls kommt
- 357 -
Betriebssystemfunktion
es bei der Restrukturieren der Tabelle zu einem Abbruch oder der Index kann nicht mehr gelesen
werden.
39.1
Default Werte
Folgende Werte sind voreingestellt, wenn keine tdbengine.ini vorhanden ist oder die entsprechenden Einträge unter [globals] fehlen:
[global]
stopcgi=0
sema=cgi
timeout=10000
logcgi=1
log=<Installationverzeichnis>/log.cgi
semadir=./
libpath=
cgibuf=65535
cdmode=0
errorlog=<Installationverzeichnis>/error.log
39.2
Aufrufe erlauben bzw. blockieren
39.2.1
Abschaltung der cgi-Aktivitäten - Stopcgi
stopcgi=0|1
Ist stopcgi gesetzt, so wird die Ausführung eines CGI-Programmes blockiert.
Über die EInstellung kann man ganz einfach die Ausführung aller oder eines bestimmten CGI-Programm blockieren.
Sinnvoll ist dies, wenn z.B. ein Backup oder die Reorganisation einer Datenbank durchgeführt werden muss und man sicherstellen möchte, dass während dieser
Zeit niemand auf die Datenbanken zugreifen kann.
Steht der Eintrag in dern tdbengine.ini unter [globals], so wirkt sich der Stop auf alle Aufrufe aus.
Man kann jedoch auch einzelne Programme stoppen, indem man für das Programm eine eigene Sektion definiert.
Beispiel für programmspezifische Einträge in der tdbengine.ini:
[processwatch]
- 358 -
Betriebssystemfunktion
sema=nosema
[test]
logcgi=1
log=/var/www/develop/zmm/logfiles/test.log
trace_time=/var/www/develop/zmm/logfiles/testTime.log
[Adressen]
log=/var/www/develop/zmm/logfiles/Adressen.log
trace_time=/var/www/develop/zmm/logfiles/Adressen.log
Das kann man mit folgendem Programmfragment erreichen:
VAR ini : STRING ini:='/home/tdbengine/bin/tdbengine.ini'
SetIdent(ini,'globals.stopcgi','1')
Pause(100) / sicher ist sicher
...
// hier wird reorganiisiert
...
SetIdent(ini,'globals.stopcgi','0')
Hinweis:
Hierzu muss freilich dem anonymen http-user ein Schreibrecht auf tdbengine.ini eingeräumt worden sein.
39.2.2
Weiterleitung im Falle stopcgi=1 - location
location=url
Ist stopcgi auf 1 gesetzt, so wird kein CGI-Programm ausgeführt, sondern die unter location angegebene Seite angezeigt. Ist keine location angegeben, so erfolgt
die Ausgabe "cgi-execution ist stopped".
Hier sollte man sich eine ordentliche HTML-Seite erstellen, die in diesem Fall angezeigt wird.
39.3
Aufrufe protokollieren
- 359 -
Betriebssystemfunktion
39.3.1
Logfiles aktivieren – logcgi / log
logcgi=0|1
Wird logcgi auf 1 gesetzt, so wird jeder CGI-Aufruf in der Datei cgi.log (im Verzeichnis der tdbengine) protokolliert:
Wurde das Protokollieren der cgi-Aktivitäten aktiviert, so wird die Protokoll-Datei cgi-log angelegt, welche die folgenden Informationen beinhaltet:
 Timestamp des Aufrufes (Serverzeit)
 IP-Adresse des Aufrufes
 Zeit1: Aufruf tdbengine, Initialisierung, Übertragung der Informationen vom Browser Ausführungszeit der tdbengine
 Zeit2: Summe der Zeiten für Semaphor-Einrichtung, Programm-Laufzeit, Semaphor-Aufhebung
 Zeit3: Übertragung aller gepufferten Informationen an den Client, Freigabe des Speichers, Schliessen aller offen Dateien
 Aufgerufene Programm
 PID des Prozesses am Server
Beispiel aus dem cgi.log:
08.11.2011 21:56:02.93 [ 192.168.2.204] - 00:00:00.00 - 00:00:00.13 - 00:00:00.00:
/var/www/develop/pep/system/baseman.prg (); PID: 22252
08.11.2011 21:56:03.43 [ 192.168.2.204] - 00:00:00.00 - 00:00:00.12 - 00:00:00.00:
/var/www/develop/pep/system/baseman.prg (); PID: 22254
08.11.2011 21:56:04.29 [ 192.168.2.204] - 00:00:00.00 - 00:00:00.00 - 00:00:00.00:
/var/www/develop/pep/program/menu.prg (act=start&rnd=0.6070449207521489); PID: 22255
Hinweis:
Die erweiterte Log-Information können Sie selbst bestimmen: CgiLog(msg).
39.3.2
Dateiname des Dateiname des Logfile festlegen log =
log=<Pfad zum Logfile>
Normalerweise wird das Logfile im gleichen Verzeichnis, in dem sich auch die tdbengine befindet, unter dem Namen "cgi.log" angelegt.
Dies kann hiermit überschrieben werden. Damit ist es möglich, dem tdbengine-Verzeichnis restriktive Rechte zu erteilen.
39.3.3
Logdatei für jedes Programm anlegen
- 360 -
Betriebssystemfunktion
Ein zusätzlicher Eintrag in der Konfigurationsdatei erlaubt es, Logfiles in andere Dateien als cgi.log zu schreiben.
[globals]
logcgi=1 log=/var/log/tdbengine/tdbengine.log
Damit wird das Logfile in die angegebene Datei geschrieben.
Zusätzlich kann dieser Eintrag jedem Programm zugeordnet werden, wodurch für einzelne Programme oder Programmgruppen separate Logfiles geschrieben
werden:
[globals]
logcgi=1
log=/var/log/tdbengine/tdbengine.log [admin]
log=./admin.log
Beispiel für die Erstellung von Logdateien zum Aufruf von adressen.prg
[adressen]
log=/var/www/develop/address/logfiles/adressen.log
trace_time=/var/www/develop/address/logfiles/adressen_time.log
Weitere Informationen zu den Logfiles und Ihrer Bedeutung findet man im Kapitel Debug
39.4
Cgibuf = CGI-Puffer definieren
cgibuf=nnnn
Alle Ausgaben (inkl. CGIWriteTemplate) erfolgen zunächst in einen Puffer. Erst am Ende des Programms, nach der Freigabe des Semaphoren (oder wenn der
Puffer voll ist), erfolgt die Übertragung der Daten. Während dieser Zeit kann das Programm bereits von anderer Seite wieder gestartet werden. Die Pufferung
erlaubt es also, die Zeitspanne des Sperrens auf die reine Programmlaufzeit zu reduzieren.
Dazu gibt es einen neuen Eintrag in tdbengine/bin/tdbengine.ini:
- 361 -
Betriebssystemfunktion
cgibuf=Größe des Puffers in Bytes (Minimum = 1024)
[globals] cgibuf=200000
[programm_a]
cgibuf=1024
Hier wird der CGI-Puffer global auf 200000 Bytes eingestellt. Nur das Programm "programm_a" muss mit 1024 Bytes auskommen. ...
Achtung:Linuxanwender können als Trennzeichen zwischen zwei Verzeichnisangaben den gewohnten Doppelpunk verwenden,Win32-Benutzer müssen das
Semikolon einsetzen.
39.5
Sema = Semaphoren für die Einrichtung von Schreibsperren
Definition des globalen Semaphoren, der verwendet wird, wenn für ein Programm kein eigener Semaphor eingerichtet wird
sema=name
Der Name darf maximal 64 Zeichen beinhalten. Bevor des CGI-Programm ausgeführt wird, wartet die tdbengine, bis der Semaphor mit dem angegebenen
Namen frei wird, um ihn dann seinerseits zu belegen. Derzeit handelt es sich dabei ausschließlich um binäre Semaphoren, sie können also immer nur von einem
Prozeß genutzt werden. Ein solcher Semaphor stellt also sicher, daß immer nur ein Prozess gleichzeitig aktiv ist. Das Semaphorenkonzept ersetzt die
Netzwerkroutinen der TurboDatenbank. Sollen Datenbanken gleichzeitig mit der Netzwerkversion des VDP (bzw. der TurboDatenbank) genutzt werden, so ist ein
spezielle Mehrplatzversion der tdbengine erhältlich.
Hinweis: Die Steuerung über Semaphoren ist wesentlich schneller als das herkömmliche File- und Record-Locking.
Erfolgt die Freigabe nicht innerhalb der durch timeout (Millisekunden) angegeben Zeit, so wird die Ausführung abgebrochen und statt dessen die in overrun
angegebene Seite ausgeführt.
Mit dem Namen "nosema" wird gekennzeichnet, daß für das CGI-Programm kein Semaphor eingerichtet wird.
- 362 -
Betriebssystemfunktion
39.6
timeout
timeout=xxxxx
39.7
overrun
overrun=url
- 363 -
Betriebssystemfunktion
39.8
Pfad zu Bibliotheken in der tdbengine.ini = Libpath (19.11.2011)
[globals]
libpath=Suchpfad
Damit kann man einen Suchpfad eingeben, der verwendet wird, wenn in ein Programm mit USES auf Bibliotheken zugegriffen wird und dabei kein kompletter
Pfad angegeben wird.
Es besteht die Möglichkeit über uses andere Module einzubinden.
Diese müssen nicht zwangsläufig im gleichen Verzeichnis stehen.
Entweder gibt man beim Einbinden den relativen oder absoluten Pfad an oder man definiert in der tdbengine.ini unter libpath den Pfad.
Achtung: Beim Compilieren wird nur die tdbengine.ini berücksichtigt, welche in dem Verzeichnis steht, in dem auch die tdbengine selbst liegt.
Einträge in einer tdbengine, die sich im Verzeichnis des Moduls befindet, werden nicht berücksichtigt.
Beispiel für das Einbinden von Modulen aus verschiedenen Verzeichnissen:
Im Modul steht dann:
Uses fulltext
Uses ../../tas/programm/esp_core
uses ../../tas/programm/espcontext
Uses ../lib/refrpools
Der erforderliche Eintrag in der tdbengine.ini sieht dann so aus:
Pfad für das Compilieren unter Windows:
[globals]
logcgi=1
libpath=s:\develop\tdblib\;s:\develop\tas\programm;;s:\develop\cim4\lib
Pfad für das Compilieren unter Linux:
[globals]
logcgi=1
libpath=/var/www/develop/tdblib/;/var/www/develop/tas/programm;
Pfad für das Compilieren sowohl unter Windows als auch unter Linux:
[globals]
- 364 -
Betriebssystemfunktion
logcgi=1
libpath=/var/www/develop/tdblib/;s:\develop\tdblib\;s:\develop\tas\programm;;s:\develop\cim4\lib;/var/www/develop/tas/program
m;
Die einzelnen Komponenten des Suchpfads können mit ";" oder ":" getrennt werden.
39.9
buffer_idents=
[globals]
buffer_idents=1
39.10
Programmbezogene Eintrage
Für jedes Programm kann eine eigene Abteilung definiert werden, deren Einträge dann die unter [globals] überschreiben. Folgende Einträge sind hier zulässig:
stopcgi
location
sema
timeout
overrun
cgibuf
log
Der Name der Abteilung lautet wie der Programmname ohne Extension.
Hinweis: stopcgi überschreibt den gleichen Wert aus [globals] nur, wenn dieser dort mit 0 festgelegt ist, andernfalls sind alle Programme gestoppt.
39.11
semadir=Verzeichnis für Semaphoren
In diesem Verzeichnis werden die Semaphoren als Dateien angelegt. Vorgabe ist .
- 365 -
Betriebssystemfunktion
errorlog=/var/log/tdbengine/error_log
cdmode=1
Mit cdmode=1 in der Abteilung [globals] der tdbengine.ini kann festgelegt werden, dass sämtliche Tabellen im R/O-Modus geöffnet werden, auch
wenn im Programm etwas anderes angegeben ist. Textdateien, die zum Schreiben geöffnet werden, werden automatisch zu Ramtexten.
nobreak=0
39.12
39.12.1
Direkt ausführbare Scripten
cdmode
Mitcdmode=1inderAbteilung[globals]dertdbengine.inikannfestgelegtwerden,dasssämtlicheTabellenimR/OModusgeöffnetwerden,auchwennimProgrammetwasanderesangegebenist.Textdateien,diezumSchreibengeöffnetwerden,werdenautomatischzuRamtexten.
- 366 -
Betriebssystemfunktion
39.13 Beispiele zur TDBengine.ini
Eine einfache tdbengine.ini könnte beispielweise so aussehen:
[globals]
stopcgi=0
location=http://my.host.de/reconstruction.html
sema=cgi
timeout=5000
overrun=http://my.host.de/overrun,html
logcgi=1
log=/var/log/tdbengine.log
libpath=/home/tdbengine/utils:/dos/c/vdp/lib
semadir=sema
[meinprogramm]
sema=sonderfall
timeout=1000
overrun=http://www.irgendwo.com/ueberlauf.html
cgibuf=200000
...
39.14
Lokale Dokumentationsdateien
Normalerweise verwendet die tdbengine die Konfigurationsdatei aus dem gleichem Verzeichnis, in dem auch das ausführbare Programm selbst liegt.
Bevor ein Programm ausgeführt wird, wechselt die tdbengine in dasjenige Verzeichnis, in dem das Programm enthalten ist. Befindet sich in diesem Verzeichnis
eine Datei tdbengine.ini, so wird diese als(ausschließliche) Konfigurationsdatei verwendet.
Das hat viele Vorteile:
1. lokale Konfigurationsdateien sind übersichtlicher
2. die Konfigurationsdatei kann für ein Projekt maßgeschneidert werden
3. bei der Weitergabe an Dritte ist die Konfiguration im Projekt enthalten
- 367 -
Betriebssystemfunktion
- 368 -
Betriebssystemfunktion
40.
Wissenswertes zu Cookies
Um ein bisschen mehr Licht ins Keks-Dunkel zu bringen empfehlen wir die Lektüre dieses Mini-HOWTOs.
Der Keks, ein unbekanntes Wesen. Kekse (Neudeutsch: "Cookies") sind leider keine Erzeugnisse des Bäckerhandwerks, sondern kleine Textketten, die auf der
Festplatte Ihres Rechners gespeichert sind, sofern Sie sie nicht löschen (oder ihre Gültigkeitsdauer abläuft).
Die auf Ihrem Rechner gespeicherten Informationen werden nur an den Server übertragen, von dem sie ursprünglich gekommen sind.
Cookies sind keine Computerprogramme und dürften die Daten auf Ihrem Rechner nicht beschädigen .
Hier! Nimm doch einen Keks. Um ein Cookie beim Anwender zu setzen müssen Sie dieses noch vor allen anderen Ausgaben im HTTP-Header übergeben.
Dazu schreiben Sie einfach eine Zeile mit folgendem Aufbau noch vor der Ausgabe der "content-type"-Zeile:
set-cookie: <NAME>=<WERT>;
oder
set-cookie: <NAME>=<WERT>; expires=<RFC-DATUM>; path=<PFADANGABE>;
Beispiel:
...
CGIWriteLn('Set-Cookie: User=Mustermann; expires=Sunday, 01-Dec-2099 12:00:00 GMT; path=/;')
CGIWriteLn('Set-Cookie: Birthday=01.02.1960; expires=Sunday, 01-Dec-2099 12:00:00 GMT; path=/;')
CGIWriteLn('content-type: text/html')
CGIWriteLn('')
// BIS hier ist alles HTTP-Header, jetzt erst kommt die HTML-Seite
CGIWriteLn('<html>')
CGIWriteLn('<head><title>cookie-test</title></head>')
CGIWriteLn('<body>Cookie gesetzt</body>')
CGIWriteLn('</html>')
...
Dieses Beispiel setzt zwei Cookies "User" und "Birthday" und versieht diese mit den Inhalten "Mustermann" und "01.02.1960". Die Lebensdauer dieser beiden
Kekse ist beachtlich, verenden sie doch erst zum Wechsel ins nächste Jahrhundert (siehe "expires").
- 369 -
Betriebssystemfunktion
Ich habe einen Keks von dir bekommen. Das Auslesen von Cookies ist mit der tdbengine geradezu ein Klacks. Ein Aufruf der Funktion CGIGetParam() reicht
bereits aus, um auf alle (erreichbaren) Cookies zuzugreifen.
Übergeben Sie dazu CGIGetParam() lediglich den Text "cookie." gefolgt vom Namen des gewünschten Plätzchens, also z.B. cookie.Birthday.
...
VAR cUser : STRING = CGIGetParam("cookie.User")
CGIWriteLn('content-type: text/html')
CGIWriteLn('')
// BIS hier ist alles HTTP-Header, jetzt erst kommt die HTML-Seite
CGIWriteLn('<html>')
CGIWriteLn('<head><title>cookie-test 2</title></head>')
CGIWriteLn('<body>Hallo '+cUser+'!<br> Ich weiss, was sie letzten Sommer getan haben!</body>')
CGIWriteLn('</html>')
...
Iss den Keks auf! Manchmal kann es durchaus sein, dass ein Cookie, welches zuvor gesetzt wurde, plötzlich nicht mehr von Nutzen ist. Beispiel: Der Anwender
meldet sich neu an und es wird ihm eine neue Session-Nummer zugewiesen. Es spricht aus Anwendersicht sicherlich nichts dagegen, wenn man so ein altes,
verdörrtes Plätzchen sachgemäss entsorgt.
Dazu setzt man am besten das Verfallsdatum auf einen Tag in der Vergangenheit.
Dies geschieht wie bereits oben beschrieben mit der ersten Zeile einer HTTP-Ausgabe.
...
CGIWriteLn('Set-Cookie: User=Mustermann; expires=Monday, 01-Jan-2000 12:00:00 GMT;')
CGIWriteLn('content-type: text/html')
CGIWriteLn('')
// BIS hier ist alles HTTP-Header, jetzt erst kommt die HTML-Seite
CGIWriteLn('<html>')
CGIWriteLn('<head><title>cookie-test</title></head>')
CGIWriteLn('<body>Cookie User gelöscht</body>')
CGIWriteLn('</html>')
...
- 370 -
Betriebssystemfunktion
Die Variable "User" in diesem Cookie hat nun keine Gültigkeit mehr - sie wird daher vom Browser auch nicht mehr an den Server übermittelt.
41.
DasServer-Projekt
Die Funktion "server" ist nun auch in der Windows-Version verfügbar, allerdings hier nur als Single-Process-Server. Das sollte aber für die allermeisten
Anwendungen keine gravierende Rolle spielen. Und weil diese Variante auch für Linux von Interesse sein kann, ist sie auch hier verfügbar:
server(Port,Prozess,Maxconnections[,Modus]) : INTEGER
Port: TCP-Port für eingehende Verbindungen
Prozess : parameterlose EASY-Prozedur
Maxconnections : maximale Anzahl von Verbindungen (Sockets) für den Server
Modus : 0 (Vorgabe) -> Single-Process, 1 (nur Linux/FreeBSD) -> Multi-Process (forked)
Die Funktion liefert nur dann ein Ergebnis, wenn sie terminiert, was sie eigentlich nicht machen sollte. In diesem Fall ist das Funktionsergebnis der Fehlercode
des betriebssystems:
13: Öffnen des privilegierten Ports verboten
98: Port bereits belegt
Die Arbeitsweise:
Wenn "server" ausgeführt wird, wartet die tdbenine auf eingehende TCP-Verbindungen am angegeben Port. Wird eine Verbindung aufgebaut, so wird die
angegebene Prozedur ausgeführt. Innerhalb dieser Prozedur sind zwei Handles verfügbar:
TextHandle 128
SocketHandle 128
Beide Handles können zur Kommunikation mit dem Klienten verwendet werden. Allerdings sollte man darauf achten, dass für das Lesen und Schreiben (bzw.
Empfangen und Senden) nur jeweils eines der beiden Handles verwendet wird, vorzugsweise das Socket-Handle (weil Textoperationen immer gepuffert werden).
Ist die Funktion server aktiv, so werden alle CGI-Ausgaben (CGIWrite, CGIWriteln, CGIWriteTemplate) auf dem Server-Socket ausgeführt.
Wenn die Prozedur beendet wird, wird die Verbindung zum Klienten beendet und das ganze Spiel beginnt von vorne.
Was kann man damit machen?
Ein Mini-Web-Server für Einzelplatz- und CD-Applikationen
- 371 -
Betriebssystemfunktion
41.1
Dokumentation
Diese Dokumentation richtet sich derzeit noch vorwiegend an Entwickler, die bereits Erfahrung in der Programmierung mit EASY (Turbo Datenbank oder Visual
Data Publisher) haben.
41.1.1
Grundsätzliches
Zur CGI-Programmierung benötigen Sie keinen Internetzugang. Sie können alle Programme offline entwickeln und testen. Sie können sich eine Umgebung
einrichten, die das Verhalten eines echten Web-Servers exakt simuliert. Sie können aber auch direkt im Web programmieren, ohne den Umweg über ftp-Up- und
-Downloads.
Wir unterstützen im Augenblick folgende Plattformen:
Win95/98
Win NT (x86)
Linux (x86)
FreeBSD
Was Sie (ausser der tdbengine) noch benötigen, ist ein http-Server wie Apache oder IIS (MS Internet Information Server). Es gibt viele http-Server, die meisten
sind kostenlos (über das Internet zu holen), manche kosten richtig Geld. Der populärste http-Server Apache ist Freeware.
Bevor Sie sich an die Installation des
tdbengine-Pakets machen, sollten Sie einen http-Server auf Ihrem Rechner funktionstüchtig installiert haben. Außerdem benötigen Sie irgendeinen Web-Browser
(Mozilla, Internet Explorer, Konqueror oder sonstiges).
Testen Sie Ihr System, indem Sie in Ihrem Browser (ohne online zu sein) die URL: http:/localhost/ aufrufen. Hier sollte jetzt die Startseite Ihres Web-Servers
erscheinen.
41.1.2
Installation
Zum Lieferumfang der tdbengine gehört install.bat (für Windows) bzw. install.sh (für Linux). Sie können dieses Programm ausführen, um die Installation interaktiv
zu erledigen. Allerdings bleibt immer etwas Handarbeit übrig, um das System aus http-Server und cgi-Interpreter zum Laufen zu bekommen. Am einfachsten
gestaltet sich das Procedere bei apache, zumindest wenn dieser in die Standardverzeichnisse installiert wurde. In diesem Fall erledigt das Installationsprogamm
alles. Allein der Neustart von apache muß von Hand erfolgen.
41.1.2.1 Apache
Speichern Sie tdbengine.tar.gz (Linux) bzw. tdbengine.zip (Win32) in einem eigenen Verzeichnis und packen Sie das Archiv dort aus.
Nun führen Sie install.sh bzw. install.bat aus:
- 372 -
Betriebssystemfunktion
./install.sh [RETURN] bei Linux
./install.bat [RETURN] bei Windows
Das Installationsprogramm fragt Sie nun nach einem Verzeichnis, in das die tdbengine installiert werden soll. Als Vorgabe haben wir
/home/tdbengine
gewählt. (In Linux-Kreisen wird häufig /sbin/tdbengine gewählt werden, weil es sich bei der tdbengine um eine nachinstallierte Anwendung handelt). Dieses
Verzeichnis muß allen Netzwerkteilnehmern zur Verfügung gestellt werden und das Recht zum Lesen und Ausführen von Progammen enthalten. Das
Schreibrecht muß ebenfalls gewährt werden, wenn über die CGI-Schnittstelle Datenbanken modifiziert bzw. andere Dateien angelegt und geschrieben werden
sollen.
Tipp:
Verwenden Sie nicht das Vezeichnis .../cgi-bin/, also das Defaultverzeichnis der Web-Server für CGI-Programme. Hier sollten die Administrationstools des
Servers belassen werden. Unter Linux sollten Sie sich als Supervisor (=root) einloggen, denn nur dann können Sie auch den http-server entsprechend
konfigurieren.
Es ist immer sinnvoll, CGI-Programme entsprechend ihrer Klassen auf verschiedene Verzeichnisse aufzuteilen, also alle Perl-Programme in ein Verzeichnis und
alle tdbengine-Programme in ein anderes. Um die Verzeichnisstruktur zu verbergen, werden sogeannte Aliase definiert. Ein weit verbreitetes Alias ist /cgi-bin/ für
das Verzeichnis mit denPerl-Programmen. In Entsprechung hierzu wollen wir für die tdbengine-Programme das Alias /cgi-tdb/ verwenden. Selbstverständlich
können Sie irgendeinen beliebigen Namen wählen.
Das macht das Installationsprogramm:
Das Installationsprogramm legt unterhalb des Installationsverzeichnisses folgende Verzeichnisstruktur an:
bin
für tdbengine und die Log-Files
database für Datenbanken
doc
für HTML-Dokumente
In das bin-Verzeichnis wird die ausführbare tdbengine kopiert. Wenn Sie wollen, dass Log-Dateien geschrieben werden, müssen Sie nachträglich das Recht zum
Anlegen von Dateien vergeben.
Die Verzeichnisse database und doc bleiben zunächst leer.
Im Installationsverzeichnis selbst werden die Programme
- 373 -
Betriebssystemfunktion
test (schreibt "Hello world...")
pdk (Programmeditor und Compiler)
ddk (Datenbank-Generator)
dmk (Datenbank-Pflege)
installiert und mit den nötigen Rechten versehen.
Nach dem Anlegen der Verzeichnisse und dem Kopieren der Dateien fragt Sie das Programm, ob es die Konfigurationsdatei "httpd.conf" anpassen soll. Falls Sie
mit apache arbeiten, können Sie hier einfach "Y" eingeben, und das Installationsprogramm führt die entsprechenden Änderungen durch.
Jetzt müssen Sie nur noch den http-Server dazu bringen, die veränderte Konfigurationsdatei zu verwenden. Wie das geht, ersehen Sie am besten aus der
mitgelieferten Dokumentation zu apache.
41.1.2.2 Manuelle Konfiguration
Falls Sie mit einem anderen Webserver arbeiten, geht es darum, diesen so zu konfigurieren, dass
a) .prg-Files als CGI-Scripten akzeptiert werden, und
b) anstelle der .prg-Files das Programm tdbengine.exe (bzw. unter Linux: tdbengine) gestartet wird.
Hier unterscheidet sich die Vorgehensweise je nach Betriebssystem und Server-Software.
41.1.2.3 Internet Information Server (Windows)
IIS ab Version 5.1: siehe hier.
Unter Windows NT müssen Sie einen Eintrag in der Registry vornehmen. Sollten Sie mit diesem Teil des Betriebssystems nicht vertraut sein, so bitten Sie
jemand, der sich wirklich auskennt, die Konfiguration für Sie zu erledigen. Tragen Sie in
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/W3SVC/Parameters/Script Map
unter dem Namen .prg den Wert \verzeichnis\bin\tdbengine.exe %1 %2 ein, wobei als Verzeichnis natürlich dasjenige angegeben werden muß, in das Sie zuvor
das Programm kopiert haben. Jetzt müssen sie den Rechner neu starten, damit die Änderung wirksam wird.
Jetzt können Sie die IIS-Administration starten und als (zusätzliches) CGI-Verzeichnis dasjenige angeben, in dem tdbengine.exe liegt. Zusätzlich vergeben Sie
ein Verzeichnis-Alias, wie beispielsweise /cgi-tdb Diese Alias-Namen für Verzeichnisse sind eine wirksamer Mechanismus, die Verzeichnisstruktur Ihrer
Maschine vor den Zugriffen anderer zu verbergen.
- 374 -
Betriebssystemfunktion
41.1.2.4 sambar (Windows)
sambar ist ein recht gutmütiger http-server für Win95/98. Auch hier müssen Sie Dateien mit der Endung .prg mit tdbengine.exe verknüpfen. Am einfachsten
erfolgt dies über den Windows-Explorer. Suchen Sie ein prg-Datei und klicken Sie diese doppelt an. Nun erscheint eine Dialog-Box, in welcher Windows
nachfragt, mit welchem Programm diese Datei verknüpft werden soll. Geben Sie hier den kompletten Pfad zu tdbengine.exe an und kreuzen Sie das Kästchen
"immer mit diesem Programm öffnen" an. Das war's auch schon. In der Sambar-Konfiguration geben Sie nun noch das Script-Alias in das Verzeichnis der
tdbengine an.
41.1.2.5 Testen
Nach der Installation aktivieren Sie in Ihrem Browser die URL: http://localhost/cgi-tdb/test.prg
Wenn jetzt die Meldung "Hello world..." auf Ihrem Bildschirm erscheint, kann es mit der CGI-Programmierung losgehen. Andernfalls bleibt Ihnen nicht anderes
übrig, als Schritt für Schritt die Installation und Konfiguration zu wiederholen.
41.1.3
Variablentypen
Mit Variablen können während der Laufzeit eines Programmes bzw. dessen Prozeduren Werte gespeichert werden. Dies können Ergebnisse aus Berechnungen
sein, Rückgaben von Funktionen oder auch Konstanten, welche bereits zur Entwicklungszeit bekannt sind.Grundsätzlich gilt das Lokalitätsprinzip, wobei die
tdbengine nur zwei Stufen kennt: globale und lokale Variablen.
Also "global" lassen sich alle Variablen bezeichnen, welche ausserhalb einer Prozedur deklariert werden. Als lokal gilt eine Variable, die innerhalb einer Prozedur
deklariert wird.
Bei gleichlautenden Variablennamen wird immer die Lokalste angesprochen.
Die Deklaration einer (oder mehrerer) Variablen wird durch das Schlüsselwort VAR eingeleitet.
VAR Variablenname1 [, Variablenname2, ... VariablennameN] : Datentyp [= Initialisierungswert]
Die Angaben in []-Klammern sind optional. Sie sehen also, dass man mehrere Variablen des gleichen Typs zugleich deklarieren und auch mit einem identischen
Wert initialisieren kann.
- 375 -
Betriebssystemfunktion
41.1.3.1 Einfache Datentypen
41.1.3.1.1 REAL
In REAL-Variablen können Fließkommazahlen mit einer Genauigkeit von 15-16 signifikanten Stellen gespeichert werden. Im Speicher belegt eine REAL-Variable
daher 8 Byte. Der Gültigkeitsbereich geht von -5.0 x 10^324 bis +1.7 x 10^308.
Beispiel-Deklarationen:
VAR nBetrag : REAL = 1234.56
VAR nUhrzeit: REAL = Now
41.1.3.1.2 INTEGER
Eine INTEGER-Variable nimmt 32-Bit-grosse Ganzzahlen im Bereich von -2147483648 bis +2147483647 auf. Dieser Datentyp ist optimal geeignet für die
Verwendung in den meisten (standard)-Funktionen, die sog. "Handle" entweder als Parameter erwarten oder aber als Ergebnis zurückliefern.
Beispiel-Deklarationen:
VAR iAnzahl : INTEGER = 9999
VAR iTable : INTEGER = OpenDB("tabelle.dat")
41.1.3.1.3 STRING
Eine STRING-Variable kann Zeichenketten mit bis zu 255 Zeichen speichern. Eine STRING-Variable belegt immer nur den Platz im Speicher, den sie tatsächlich
für den zu speichernden Inhalt benötigt zzgl. einem Längenbyte. Die tatsächliche Länge des Inhaltes wird im allerersten Byte abgelegt, was auch die Begrenzung
auf maximal 255 Zeichen erklärt.
Beispiel-Deklarationen:
VAR cTabelle : STRING = "../database/tabelle.dat"
- 376 -
Betriebssystemfunktion
VAR cName
: STRING = GetQueryString("name")
41.1.3.1.4 CHAR
Eine CHAR-Variable kann genau ein Zeichen / Byte aufnehmen. Dieser Datentyp ist (genau so wie BYTE) relativ neu und wurde hauptsächlich zur Verwendung
mit (binären) Stream-Operationen eingeführt.
Beispiel-Deklarationen:
VAR chSemikolon : CHAR = ';'
VAR chABC
: CHAR = Chr(65)
41.1.3.1.5 ARRAYS UND ARRAY-TYPEN
Aus den, oben aufgeführten, einfachen Datentypen können ein- und mehrdimensionale Arrays gebildet werden. Ausserdem gibt es zusätzliche Datentypen, die
ausschliesslich zur Verwendung als Array gedacht sind.
Für Arrays gilt grundsätzlich: Vor der ersten Wertzuweisung bzw. dem ersten lesenden Zugriff auf ein Array, muss dieses initialisiert werden.
Diese Initialisieriung kann zum einen bei der Deklaration geschehen (und steht somit bereits beim Kompilieren fest) oder zum anderen erst zur Laufzeit, was es
erlaubt, Arrays dynamisch zu dimensionieren.
Die dynamische Initialisierung erfolgt letztlich immer mit Hilfe der Funktion InitArray().
Beachten Sie, wenn Sie Arrays als Abbild einer Tabelle verwenden (als temporäre Markierungslisten zum Beispiel), dass physikalische Satznummern immer bei
1 anfangen, Arrays jedoch bei 0. Lassen Sie dann am besten einfach das erste Element im Array leer. Das erlaubt eine Referenzierung anhand der Satznummer
, als
VAR aAutoID : INTEGER[9999]
GetAuto(db,aAutoID)
x := aAutoID[RecNo(db)] // x mit Laufender Nr. des akt. Datensatzes befüllen,
41.1.3.1.6 TBITS[]
- 377 -
Betriebssystemfunktion
Ein Array des Typs TBITS entspricht einer Aneinanderreihung von einzelnen Bits, welche einen der beiden Zustände 0 oder 1 annehmnen können. Damit lässt
sich sehr leicht die interne Markierungsliste einer Tabelle abbilden, in eine Datei speichern und wieder laden. Auch das Kombinieren von Suchergebnissen
anhand binärer UND und ODER Operationen (siehe BitAnd(), BitNot(), BitOr() und BitAndNot()) ist damit leicht und vor allen Dingen schnell möglich.
Die Initialisierung eines TBITS-Arrays erfolgt durch Angabe der Anzahl zu verwendender Bits.
Beispiel-Deklarationen:
VAR aMarks : TBITS[100]
VAR aRecs : TBITS[]
InitArray( aRecs[FileSize(db)] )
41.1.3.1.7 INTEGER[]
Ein INTEGER[]-Array speichert beliebig viele Integer-Werte in einer oder mehreren Dimensionen. Der Speicherbedarf lässt sich daher leicht ermitteln, nämlich
mittels der Formal "Anzahl Dimensionen * Maximaler Index * 4 Byte".
Beispiel-Deklarationen:
VAR aAutoIDs : INTEGER[100]
VAR aKoordinaten : INTEGER[,]
InitArray( aKoordinaten[FileSize(db) ,2] )
aKoordinaten[1,1] := 10; aKoordinaten[1,2] := 40
...
41.1.3.1.8 CHAR[]undBYTE[]
EIn CHAR[]- bzw. BYTE[]-Array belegt für jeden Wert genau 1 Byte Speicherplatz. Eindimensionale Byte-Arrays werden im Zusammenhang mit den Socketsowie den Stream-Funktionen sehr interessant. Sie dienen dort als Zwischenpuffer für die ein- bzw. ausgehenden Datenströme.
Ein CHAR[]-Array kann ähnlich wie ein String mit ganzen Zeichenketten befüllt werden. Dazu muss der Index, ab dem der String eingefügt werden soll,
angegeben werden.
Sowohl BYTE[] als auch CHAR[]-Arrays werden mit Chr(0) initialisiert.
- 378 -
Betriebssystemfunktion
Beispiel-Deklaration:
VAR aKiloString : CHAR[1024]
VAR aIncoming : BYTE[]
InitArray( aIncoming[12345] )
...
aKiloString[0] := "Dies ist ein STRING, der direkt IN einem CHAR[] abgelegt wird"
41.1.4
Textdateien
Textdateien spielen im Zusammenhang mit der CGI-Programmierung eine große Rolle. So handelt es sich beispielsweise bei Templates um Textdateien, und
auch Sessioninformationen werden normalerweise in einer Textdatei gepeichert und daraus wieder gelesen. Ebenso wichtig sind Konfigurationsdateien, mit
denen das Verhalten von Programmen festgelegt wird und die in vielen Fällen sog. System-Datenbanken ersetzen können.
Eine Spezialform der Textdateien liefern die virtuellen Texte der tdbengine. 248 dieser Texte existieren nur während der Laufzeit des Programms im
Arbeitsspeicher. Sie sind derzeit auf 2 GByte beschränkt. Der Vorteil von virtuellen Texten liegt einerseits in der schnellen Bearbeitung und andererseits in der
teilweisen Kompatibiltät zu einfachen Zeichenketten. So sind beispielsweise die Funktionen OemToAnsi() und AnsiToOem() auch auf virtuelle Texte
anwendbar.
Virtuelle Texte sind dadurch ausgezeichnet, daß ihr Dateiname mit "ramtext:" beginnt. Der Name dieser Datei entspricht dem Rest von Path.
"ramtext:name1" spricht den virtuellen Text "name1" an.
Ein besonderer virtueller Text hat den Namen "ramtext". Er wird durch die CGI-Funktionen LoadTemplate() und CGIWriteTemplate() initialisiert und bildet
die Basis der Template-Funktion Subst().
CGI-Variablen mit dem Namen "text:..." legen automatisch virtuelle Texte gleichen Namens an.
In einem HTML-Formular befindet sich folgende Konstruktion:
<textarea cols=60 rows=20 name="text:inhalt"></textarea>
Das über einen submit-Schalter aufgerufene Programm hat den Inhalt des Textareas komplett im virtuellen Text "ramtext:text:inhalt" und kann beispielsweise so
ausgelesen werden:
- 379 -
Betriebssystemfunktion
VAR t : INTEGER
IF t:=Reset("ramtext:text:inhalt")
CGIWriteLn('Inhalt der Textvariablen: <br><pre>')
WHILENOTEOT(t) DO
CGIWriteHTML(Read(t))
END
CGIWriteLn('</pre><p>')
Close(t)
ELSE
CGIWriteLn('Fehler: Textvariable NICHT gefunden<br>')
END
41.1.4.1 TEXTDATEIEN ÖFFNEN
Reset(path : STRING) : INTEGER
öffnet eine Textdatei zum Lesen
Rewrite(path : STRING) : INTEGER
erzeugt eine neue Textdatei und öffnet sie zum Schreiben
TAppend(path : STRING) : INTEGER
öffnet eine bestehende Textdatei zum Weiterschreiben
Wichtig: Ist das Funktionsergebnis 0, so darf keinesfalls in diese Datei geschrieben oder daraus gelesen werden.
Expertentip: Der FileHandle 0 bezeichnet die Konsole. Damit kann direkt auf die Kanäle stdin und stdout zugegriffen werden. Beachten Sie aber, daß stdin bei
der Initialisierung der CGI-Variablen komplett gelesen wird. Ein read() bzw. readln() mit dem FileHandle 0 bewirkt somit immer, daß das CGI-Programm
hängt, denn es wartet auf eine Eingabe, die nie mehr kommen kann!
Kann aus irgendeinem Grund die Textdatei nicht geöffnet werden (Datei ist nicht vorhanden, keine Rechte zum Anlegen der Datei etc.), so wird ein Laufzeitfehler
ausgelöst. Diesen kann man mit SetPara('ec 1') (siehe Laufzeitschalter) abfangen:
SetPara('ec 1');
IF t:=Reset('text/meintext.txt')=0
THEN ... Fehlerbehandlung mit TDB_LastError
- 380 -
Betriebssystemfunktion
ELSE
... hier kann mit dem Text gearbeitet werden
END
SetPara('ec 0');
41.1.4.2 LESEN AUS TEXTDATEIEN
Read(filehandle : INTEGER[,nchars | delchar : CHAR]) : STRING
ReadLn(filehandle : INTEGER) : STRING
Beide Lesefunktionen liefern eine Zeichenkette mit maximal 255 Zeichen. read() liest alle Zeichen, readln() bis zum nächsten Linefeed (asc(10)), wobei
Zeilenvorschub (asc(13)) überlesen wird. Beide Funktionen beenden das Lesen, wenn das physikalische Dateiende oder ^Z erreicht wird.
Wird bei read() kein zusätzlicher Parameter angegeben, so wird genau ein Zeichen gelesen. Wird nchars (INTEGER) angegeben, so werden (maximal) nchars
Zeichen gelesen. Wird hingegen ein delchar angegeben (beispielsweise ^I=TAB) so wird bis zu diesem Zeichen gelesen. Das Zeichen selbst wird nicht
zurückgegeben. In jedem Fall werden maximal 255 Zeichen zurückgeliefert.
41.1.4.3 SCHREIBEN IN TEXTDATEIEN
Write(filehandle : INTEGER; s : STRING) : STRING
WriteLn(filehandle : INTEGER; s : STRING) : STRING
Bei writeln wird eine Kombination aus Zeilenvorschub und Linefeed nach dem String in die Datei geschrieben.
41.1.4.4 DATEIEN SCHLIEßEN
Close(filehandle : INTEGER) : INTEGER
Das Ergebnis ist der IO-Fehlercode des Betriebssystems.
41.1.4.5 DATEIENDE ERMITTELN
EOT(filehandle : INTEGER) : 0|1
Der typische Fall, wenn eine ganze Textdatei bearbeitet wird:
- 381 -
Betriebssystemfunktion
IF t:=Reset(pfad_zur_datei) THEN
WHILENOTEOT(t) DO
c:=Read(t);
...
END
Close(t)
END;
41.1.4.6 DIE ARBEIT MIT TEMPLATES
Ein Template ist ein Textbaustein, der zunächst in den Arbeitsspeicher geladen wird, um innerhalb des Textes Ersetzungen durchzuführen. Meist handelt es sich
um HTML-Seiten bzw. Auszügen daraus. Damit ist es möglich, im Team mit Web-Designern zu arbeiten.
41.1.4.7 TEMPLATE LADEN
LoadTemplate(Pfad : STRING) : INTEGER
Entspricht der Funktion
CopyFile(pfad,"ramtext")
41.1.4.8 TEMPLATE AUSGEBEN
CGIWriteTemplate : INTEGER
Entspricht der Funktion
CopyFile("ramtext","con")
41.1.4.9 ERSTETZUNG AUSFÜHREN
Subst(Source : STRING; DbNo : INTEGER; Field : INTEGER|STRING [; Mode : INTEGER]) : 0|1
Subst(Source,Target : STRING [; Mode : INTEGER]) : 0|1
DbNo ist ein Database-Handle, der mit OpenDB() erzeugt wird.
- 382 -
Betriebssystemfunktion
Field ist entweder eine Feldnummer oder ein Feldbezeichner als String
Target ist String. Beginnt der String mit "extern:" oder "ramtext:", so wird damit ein entsprechender externer Text bezeichnet:
extern:
ramtext:
der Rest des Strings bezeichnet einen Dateinamen
der Rest des Strings bezeichnet einen Ramtext bzw. eine CGI-VariablE, deren Namen mit "text:" beginnt.
Mode ist eine Addition der einzelnen Grundmodi
0 : Standardersetzung ohne Bearbeitung (default)
1 : Wird vor der Ersetzung nach HTML übersetzt
2 : Ersetzung erfolgt im Ansi-Zeichensatz
4 : LF wird durch LF+'<br>' ersetzt
8 : ExtNote wird nach der ersten Ersetzung gelöscht (nur in Verbindung mit 1)
16 : Externer Text ist im ASCII-Zeichensatz (nur in Verbindung mit externen Texten)
32: Nur Body-Teil wird gelesen (nur in Verbindung mit externen HTML-Texten)
Das Funktionsergebnis ist 1, wenn die Ersetzung durchgegführt wurde (also im positiven Fall), ansonsten 0. Um alle Vorkommen zu ersetzen kann also
beispielsweise folgende Konstruktion verwendet werden:
WHILESubst(....) DOEND
41.1.4.10 KONFIGURATIONSDATEIEN
Die tdbengine unterstützt in besonderem Maße Konfigurationsdateien, die nach folgendem Schema aufgebaut sind:
[Gruppe 1]
Eintrag1=...
Eintrag2=...
...
[Gruppe 2]
Eintrag1=...
Eintrag2=...
...
Jeder Eintrag kann aus bis zu 255 Zeichen bestehen.
Zur Bearbeitung von derartigen Dateien stellt EASY zwei Funktionen zur Verfügung: SetIdent() und GetIdent().
Zur Bearbeitung von derartigen Dateien stellt EASY zwei Funktionen zur Verfügung: SetIdent() und GetIdent().
- 383 -
Betriebssystemfunktion
SETIDENT(Konfigurationsdatei,Eintrag,Wert)
GETIDENT(Konfigurationsdatei,Eintrag)
schreibt Eintrag=Wert in Konfigurationsdatei
liefert den zum Eintrag gehörenden Wert
Einträge werden dabei in der Form »Gruppe.Eintrag« angegeben.
PROCEDUREMain
VARDEF ini : STRING
ini:='test.ini'
CGIWriteLn('content-type: text/html')
CGIWriteLn('')
SetIdent(ini,'Administrator.Name','Hans Huber')
SetIdent(ini,'Administrator.Passwort','geheim')
SetIdent(ini,'Datenbank.Adressen','database/adressen.dat')
CGICloseBuffer
CGIWrite('<pre>')
CopyFile(ini,'con')
CGIWrite('</pre>')
ENDPROC
Dieses kleine Programm legt die Datei »test.ini« mit folgendem Inhalt an:
[Administrator]
Name=Hans Huber
Passwort=geheim
[Datenbank]
Adressen=database/adressen.dat
Anmerkung: Die erzeugte Datei wird dann auch noch auf dem Bildschirm ausgegeben. Wichtig ist hier der Einsatz von
CGICloseBuffer(), damit der interne
Puffer vor dem Kopiervorgang ausgegeben wird.
Mit GetIdent() können die einzelnen Einträge ausgelesen werden.
GetIdent('test.ini','Administrator.Name) -> 'Hans Huber'
GetIdent('test.ini',Datenbank.Adressen) -> 'database/adressen.dat'
- 384 -
Betriebssystemfunktion
Hinweis : Beim ersten Zugriff auf eine Konfigurationsdatei wird diese komplett in den Arbeitsspeicher des Computers gelesen und über
eine Baumstruktur dem
Programm zur Verfügung gestellt. Deshalb ist der Zugriff extrem schnell.
41.1.5
Systemfunktionen
Folgende Funktionen werden hier besprochen:
GetDir
aktuelles Verzeichnis ermitteln
MakeDir
Verzeichnis anlegen
DelDir
Verzeichnis löschen
ChDir
aktuelles Verzeichnis wechseln
FirstDir
ersten Verzeichniseintrag ermitteln
NextDir
nächsten Verzeichniseintrag ermitteln
DirInfo
Verzeichniseintrag holen
today
Systemdatum
now
Systemzeit
DelFile
Datei löschen
CopyFile
Datei kopieren
DiskFree
freien Speicher in Partition ermitteln
GetEnv
Umgebungsvariable ermitteln
41.1.5.1 ALLGEMEINES
Um systemübergreifend programmieren zu können, können alle Verzeichnisangaben im Unix-Format angegeben werden (also /home/tdbengine/test.prg statt
home\tdbengine\test.prg unter Windows). Beachten Sie bitte, daß unter Unix Groß-/Kleinschreibung in Dateinamen unterschieden wird. Unser Tip: verwenden
Sie nur Kleinbuchstaben und keine Sonderzeichen in Dateinamen, dann sind Ihre Anwendungen sicher von einer Plattform auf die andere portierbar.
Bitte beachten Sie, daß CGI-Programme normalerweise immer nur die Rechte des (in den meisten Fällen) anonymen Internet-Anwenders haben. Diesen muß
also im Verzeichnis, in dem die CGI-Programme liegen, und im Verzeichnis der tdbengine wenigstens das Ausführungsrecht und das Leserecht eingeräumt
- 385 -
Betriebssystemfunktion
werden. Wird mit Datenbanken gearbeitet, so muß entsprechend deren Zugriffen (nur lesend, lesend und schreibend) im Datenbank-Verzeichnis die Rechtegabe
erfolgen. Selbst wenn keine neuen Tabellen angelegt werden sollen, so erfordert eine eventuelle Volltext-Indizierung auch das Recht, Dateien anzulegen.
Obwohl es den Sicherheitsaspekten des Internets nicht so ganz entspricht, ist es im Falle der CGI-Programmierung gebräuchlich, im CGI-Verzeichnis (und in den
darunterliegenden Verzeichnissen) dem anonymen Internet-Anwender alle Rechte einzuräumen. Selbstverständlich darf das Verzeichnis via Anonymous-ftp
niemals freigegeben werden! Damit übernehmen Sie als CGI-Programmierer eine ganze Menge Verantwortung, denn der Internet-Anwender kann alle
Programme ausführen, die hier vorliegen. Sie müssen also daür sorgen, daß
keine fremden Programme hier gespeichert werden können, und
Ihre Programme keinen Mißbrauch erlauben.
Falls Sie CGI-Administrationsprogramme ebenfalls im Internet installieren (wie etwa unser program development kit), sollten Sie diese ausschließlich in einem
(durch Benutzer-Identifizierung) geschützen Verzeichnis ablegen.
41.1.5.2 DAS AKTUELLE VERZEICHNIS ERMITTELN
GetDir(Laufwerknummer : REAL) : STRING
GetDir() liefert das aktuelle Verzeichnis. Ist die Laufwerknummer 0, so wird das aktuelle Verzeichnis des aktuellen Laufwerks geliefert. Unter
Unix gibt es
keine verschiedenen Laufwerke, deshalb muß hier immer 0 angegeben werden.
Hinweis:Die tdbengine wechselt immer in das Verzeichnis, in dem das auszuführende prg-File ist.
41.1.5.3 ANLEGEN UND LÖSCHEN VON VERZEICHNISSEN
MakeDir(Dir : STRING) : REAL
Legt ein Verzeichnis an, falls der Anwender das Recht dazu hat. Konnte das Verzeichnis angelegt werden, so liefert die Funktion 0, andernfalls den Fehlercode
des Betriebssystems.
DelDir(Dir : STRING) : REAL
Löscht das angegebene Verzeichnis, falls der Anwender das Recht dazu hat. Es können nur leere Verzeichnisse gelöscht werden. Die Funktion liefert 0, wenn
das Verzeichnis gelöscht wurde, andernfalls den Fehlercode des Betriebssystems.
41.1.5.4 VERZEICHNIS WECHSELN
ChDir(Dir : STRING) : REAL
- 386 -
Betriebssystemfunktion
Wechselt in das Verzeichnis. Liefert 0, wenn der Wechsel erfolgreich war, andernfalls den Fehlercode des Betriebssystems.
- 387 -
Betriebssystemfunktion
41.1.5.5 SUCHE IN VERZEICHNISSEN
FirstDir(Pfad,Option : STRING) : STRING
NextDir : STRING
Unterschiede:Linux und Windows
Die Versionen der tdbengine für Linux und Windows sind weitgehend identisch, ebenso die verwendeten Dateiformate. Unterschiede treten nur da auf, wo auch
das jeweilige Betriebssystem seine Besonderheiten hat
Die Timer-Auflösung bei den Semaphoren beträgt bei Linux 1 Sekunde, unter Windows 1/1000 Sekunde, was jedoch in der Praxis keine Rolle spielt.
Bei den Dateiattributen, die von den Directory-Funktionen FirstDir und NextDir geliefert werden, wird unter Linux nur "d" (=Directory) gesetzt, dafür werden die
Dateirechte komplett ausgegeben.
FirstDir, NextDir:
Zeichen
Anzahl
Win9x, WinNT
Linux
1
63
Dateiname
Dateiname
64
7
Zeichen Anzahl Win9x, WinNT
Linux
1
63
Dateiname
Dateiname
64
7
Dateiattribute
Dateiattribute
64
R=Readonly
d=directory
65
H=Hidden
66
S=System
67
V=Volume
68
D=Directory
69
A=Archiv
71
12
Dateigröße
Dateigröße
84
10
Datum
Datum der letzten Änderung
96
8
Zeit
Zeit der letzten Änderung
110 9
Zugriffsrechte
110
x Ausführungsrecht Besitzer
111
w Schreibrecht Besitzer
112
r Leserecht Besitzer
113
x Ausführungsrecht Gruppe
114
w Schreibrecht Gruppe
115
r Leserecht Gruppe
- 388 -
Betriebssystemfunktion
116
x Ausführungsrecht Rest
117
w Schreibrecht Rest
118
r Leserecht Rest
128 127
Verzeichnis
Verzeichnis
Die Funktion ChMod(Dateiname, Rechte : STRING) : REAL setzt unter Linux die Rechte an der Datei neu, je nach den Rechten, mir der diese Funktion
ausgeführt wird (also nur als root bzw Besitzer der Datei). Die neuen Rechte werden als 'rwxrwxrwx' angegeben, wobei das erste Trippel für die Rechte des
Benutzers, das zweite für die Rechte der Gruppe und das letzte für die Rechte des Rests der Welt gilt. Das Funktionsergebnis liefert den Linux-Fehlerstatus.
Hinweis: Neben der wesentlich dedizierteren Rechte-Vergabe ist unter Linux die Unterscheidung der Groß-/Kleinschreibung zu beachten!
Im Zuge der Vereinheitlichung der Programme, und weil es sich bei allen Adressierungen im Internet um Unix-Adressierungen handelt, sollten auch unter
Windows alle Pfadangaben mit dem normalen Schrägstrich "/" angegeben werden. Die tdbengine akzeptiert beide Schreibweisen.
Der Pfad ist eine Verzeichnisangabe, in der auch Wildcards erlaubt sind. Unter Unix sind auch erweiterte Muster der Form "*abc" möglich. Die Option wird nur
unter Windows ausgewertet. Es handelt sich dabei um einen String mit einer Kombination folgender Zeichen:
D sucht auch Verzeichnissen
H sucht nach versteckten Dateien
S sucht nach Systemdateien
Zusätzlich wird immer nach einfachen Dateien gesucht.
Hinweis: Änderung gegenüber Vorgängerversion VDP und TDB!
Das Ergebnis der Funktion ist ein Leerstring, wenn kein (weiterer) Verzeichniseintrag vorliegt, andernfalls ein String mit folgendem Aufbau:
Zeichen
Inhalt
1..63
Dateiname
64..70
Dateiattribute
71..82
Dateigröße
84..94
Datum der letzen Änderung
95..102
Zeit der letzten Änderung
110..118
Rechte in der Form uuugggooo (nur bei Linux)
128..255
Verzeichnis (absolut)
Ausgabe aller Dateien mit der Extension "html"imVerzeichnis"/home/tdbengine/doc"
VAR dir_entry : STRING
- 389 -
Betriebssystemfunktion
...
dir_entry:=FirstDir('/home/tdbengine/doc/*.html','')
WHILE dir_entry DO
CGIWriteLn(RTrim(dir_entry[1,63]+'<br>')
dir_entry:=NextDir
END
...
Hinweis: Die tdbengine liest beim Aufruf von FirstDir() alle passenden Verzeichniseinträge ein und stellt sie dann sukzessive zur Verfügung. Beim nächsten
Aufruf von FirstDir() (und am Programmende) wird dieser interne Puffer wieder geleert. Diese Vorgegehensweise hat folgende Konsequenzen:
es werden keine Systemressourcen belegt
NextDir() ist eine tdbengine-interne Funktion und führt zu keinen Systemcall
FirstDir() - NextDir() - Sequenzen können nicht geschachtelt werden
Wenn nur die Systemdaten (Datum, Größe, Existenz etc.) einer einzelnen Datei abgefragt werden soll, kann die Funktion DirInfo() eingesetzt werden:
DirInfo(Path : STRING) : STRING
Die Funktion liefert das selbe Ergebnis wie FirstDir(Path,''), kann aber auch innerhalb einer FirstDir() - NextDir() - Sequenz eingesetzt werden.
41.1.5.6 SYSTEMDATUM UND SYSTEMZEIT
Today : REAL
Liefert das aktuelle Datum als Tage seit dem 1.1.1900. Mit DateStr(today) erhalten Sie das aktuelle Datum als String.
Now : REAL
Liefert die aktuelle Uhrzeit als Anzahl der Minuten seit Mitternacht.
Hinweis:
Frac(Now)*60
liefert die Sekunden mit einer Auflösung 1/1000 s
Mit
TimeStr(Now)
- 390 -
Betriebssystemfunktion
erhalten Sie die aktuelle Uhrzeit als String
TimeStr(now)
18:32
TimeStr(now,0)
18:32:47
TimeStr(now,3)
18:32:47.251
41.1.5.7 ALLGEMEINE DATEINFUNKTIONEN
DelFile(Pfad : STRING) : REAL
Löscht die mit "Pfad" angegebene Datei, wenn es sich nicht um ein Verzeichnis handelt, und wenn der anonyme http-User das Recht dazu hat. Unter Windows
wird ein eventuell bestehender Schreibschutz ignoriert. Das Funktionsergebnis ist 0, wenn die Datei gelöscht werden konnte, andernfalls der Fehlercode des
Betriebssystems.
CopyFile(p_von, p_nach : STRING) : REAL
Damit wird die Kopie einer Datei angelegt.
p_von darf sein:
beliebiger Pfad innerhalb des Verzeichnisbaums
interne Textdatei (Ramtext)
URL (nur http://*, seit Version 6.2.6)
p_nach darf sein:
beliebiger Pfad innerhalb des Verzeichnisbaums
interne Textdatei (Ramtext)
"con" (für Standardausgabe)
Hinweis: Wird mit CopyFile() auf die Standardausgabe kopiert, muss zurvor CgiCloseBuffer() aufgerufen werden, da CopyFile() die Pufferung
umgeht.
- 391 -
Betriebssystemfunktion
41.1.5.8 FREIEN SPEICHER IN PARTITION ERMITTELN - diskfree
DiskFree(drive : REAL) : REAL
drive: 0 = aktuelles Laufwerk (Win32) bzw. Partition, in der das aktuelle Verzeichnis liegt (Linux, FreeBSD)
1 = A: (nur Win32)
2 = B: (nur Win32)
...
Rückgabewert: Freier Platz in Laufwerk / Partition bzw. -1, falls Laufwerk nicht verfügbar
- 392 -
Betriebssystemfunktion
41.1.5.9 UMGEBUNGSVARIABLE ERMITTELN
GetEnv(Name : STRING) : STRING
GetEnv() liefert den Wert der Environment-Variablen mit dem angegebenen Namen.
Zusätzlich steht nach jedem Aufruf ein Ramtext mit dem Namen "ramtext:environment" zur Verfügung, der ebenfalls die
Environmentvariable enthält. Damit kann
auch auf Environment-Variablen zugegriffen werden, deren Inhalt 255 Zeichen übersteigt.
CGICloseBuffer
CGIWrite("<pre>")
GetEnv("HTTP_ACCEPT")
CopyFile("ramtext:environment","con")
CGIWriteLn('</pre>')
- 393 -
Betriebssystemfunktion
42.
Das Semaphorenkonzept der tdbengine
42.1
VIELE TDBENGINES GLEICHZEITIG
Theoretisch können beliebig viele Instanzen der tdbengine gleichzeitig arbeiten. Der Speicherbedarf liegt pro Instanz bei etwa 580 KByte (plus aktuelle
Variablen), so dass bei einem freien Speicher von nur 64 MByte mehr als 100 tdbengines gleichzeitig arbeiten können. Das sollte selbst auf gut gesuchten
Internet-Seiten locker ausreichen.
Wenn die durchschnittliche Laufzeit inclusive Programmladen/Initialiserung/Aufräumen (unter Last, also wenn bereits viele andere Programme arbeiten) einer
Instanz bei 0.1 Sekunde liegt, so lassen sich unter der oben genannten Annahme etwa 3.600.000 Aufträge pro Stunde realisieren, ein Wert, von dem SiteBetreiber nur träumen können.
Freilich gibt es Programme, die länger als 0.1 Sekunde für ihre Arbeit benötigen. Immer, wenn es in den Sekundenbereich geht, sollte man immer über
Optimierungsschritte nachdenken. Aber auch bei einer durchschnittlichen Laufzeit von 1 Sekunde wären hier immer noch 360.000 Aufrufe pro Stunde möglich,
wenn, ja wenn die Prozesse wirklich unabhängig voneinander arbeiten könnten.
42.2
SYNCHRONISATION NOTWENDIG
Leider ist dem nicht so. Denn wenn die Programme irgendwelche Daten schreiben, muss dafür gesorgt werden, dass sie sich dabei nicht in die Quere kommen.
Ohne an dieser Stelle wirklich in Detail zu gehen, sei hier nur ein Beispiel angeführt: Der schreibende Zugriff auf den Index einer Tabelle hat mitunter zur Folge,
dass der komplette BTree (die tdbengine speichert Indizes und manchmal auch Tabellen in solchen Strukturen ab) umgebaut wird. Greift wärend der
Umbauphase ein anderer Prozess auf diesen Index zu, so erhält er nichts als Datenchaos. Transaktionen (also die Übergänge zwischen konsistenten Zuständen
der Daten) müssen gesichert werden. In der Prozesstechnik spricht man bei solchen Übergängen auch von "kritischen Zuständen", den ein Prozess isoliert
durchziehen muss, und wärend dessen er nicht von anderen Prozessen gestört werden darf.
Prozesse, die einen gemeinsamen Datenbestand bearbeiten, müssen demnach synchronisiert werden. Die Synchronisation der Prozesse erfolgt bei der
tdbengine mit sogeannten Semaphoren. Einen Semaphor kann man sich als Wächter vorstellen, der am Anfang eines kritischen Weges steht und darauf achtet,
dass immer nur soviele Prozesse diesen Weg betreten, wie dieser Weg verkraften kann. Handelt es sich dabei nur um einen einzigen Prozess (das ist der
häufigste Fall), der auf den Weg geschickt werden darf, so spricht man auch von einem "binären" Semaphor (go - wait). Derzeit unterstützt die tdbengine nur
binäre Semaphoren.
Anfangs geht die tdbengine immer ganz auf "Nummer Sicher": Für alle Programme ist ein einziger Semaphor zuständig, und dieser lässt nur immer einen
Prozess passieren. Alle anderen müssen warten. Und jetzt schaut die obige Rechnung schon anders aus. Nehmen wir wieder eine durchnittliche Laufzeit von 0.1
Sekunde. Jetzt schaffen wir maximal 36.000 Auträge pro Stunde. Die Möglichkeit der parallelen Bearbeitung bring uns keinen höheren Datendurchsatz, sondern
liefert und nur ein Auffangbecken für maximal 100 Aufträge, so dass Spitzen bis zu 1.000 Anforderungen pro Sekunde abgefangen werden können, die dann
einfach in die Warteschleife eingereiht werden.
- 394 -
Betriebssystemfunktion
42.3
...UND SCHLUCKT PERFORMANCE
Angenommen, wir haben nun ein Programm, das für seine Arbeit eine recht lange Zeit benötigt - sagen wir einmal 30 Sekunden - weil beispielsweise ein großer
Datenbestand exportiert wird. Während dieser Zeit werden alle anderen Prozesse auf die Warteliste gesetzt, obwohl sie unter Umständen mit diesem
Datenbestand garnichts zu tun haben. Es hagelt "cgi overruns", und die Web-Besucher wenden sich anderen Seiten zu. So darf es nicht sein!
Und so ist es auch nicht. Die anfänglich rigorose Sicherheitsstrategie der tdbengine beruht darauf, dass sie keine Ahnung davon hat, was die einzelnen
Programme machen, auf welche Datenbestände sie zugreifen und ob dabei überhaupt irgendwelche kritischen Zustände auftreten können. Die tdbengine weiss
es nicht, aber Sie wissen es. Und deshalb können Sie auch neue Semaphoren kreieren und damit den Datenfluss einerseits sicher und anderseits performant zu
machen. Und genau darauf kommt es an.
42.4
SO MACHEN WIR ES RICHTIG
Für den Eingriff gibt es zwei Möglichkeiten: Einträge in die Konfigurationsdatei "tdbengine.ini" und spezielle Semaphorenfunktionen im Programm. Wir wollen uns
zunächst den Möglichkeiten der Konfigurationsdatei zuwenden.
42.5
SEMAPHOREN IN DER KONFIGURATIONSDATEI
Seit der Version 6.2.7 der tdbengine gibt es die Möglichkeit, lokale Konfigurationsdateien anstelle der globalen zu verwenden. Dieses Feature wollen wir gleich
ausnutzen und in dem Verzeichnis, in dem sich die kompilierten EASY-Module befinden, eine Datei "tdbengine.ini" einrichten, die zunächst einmal folgende
Einträge enthält:
[globals]
logcgi=1
log=./log/cgi.log
semadir=./sema
stopcgi=0
location=http://www.meinserver.de/globale_aktualisierung.html
timeout=10000
overrun=http://www.meinserver.de/zu_viel_los_hier.html
Jetzt müssen Sie noch die Verzeichnisse "log" und "sema" unterhalb des prg-Verzeichnisses anlegen und dem Ausführer der tdbengine (also meist dem
anonymen http-Klienten) die dort die Rechte zum Anlegen und Verändern von Dateien geben.
Dann sollten Sie eine hübsche HTML-Datei "zu_viel_los_hier.html" anlegen, die mit freundlichen Worten darauf hinweist, dass sich gerade tausende von
Menschen auf dieser Seite tummeln und das System deshalb überlastet ist. Dadurch wird die garstige "cgi-overrun"-Meldung überschrieben. Diese Seite sollte
über den angegebenen URL genauso erreichbar sein wie die Seite "globale_aktualisierung.html", in der Sie darauf hinweisen, dass der Datenbestand gerade
überarbeitet wird und die dynamischen Inhalte in Kürze wieder zur Verfügung stehen werden.
- 395 -
Betriebssystemfunktion
Schauen wir uns an dieser Stelle die Einträge in der Konfigurationsdatei an:
Mit logcgi=1 wird die tdbengine dazu angehalten, alle Aktivitäten in einem Logfile zu protokollieren. Damit erhalten wir einen genauen Überblick Den Pfad zum
Logfile stellen wir mit log=./log/cgi.log ein.
Mit semadir geben wir das Verzeichnis an, in dem die Semaphoren angelegt werden. Diese werden nämlich von der tdbengine als (leere) Dateien betrachtet, die
mit Systemaufrufen komplette gesperrt und wieder freigegeben werden. Das hat den großen Vorteil, dass die Sperren auch dann aufgehoben werden, wenn ein
tdbengine-Prozess nicht sauber terminiert, was zwar niemals vorkommen sollte, was aber nicht mit absoluter Sicherheit ausgeschlossen werden kann.
Unter timeout wird die Zeit (in Millisekunden) angegeben, die ein Prozess wartet, bevor die Meldung unter overrun ausgegeben wird. Wir stellen sie hier auf 10
Sekunden ein, was für Webanwendungen erst einmal einen guten Wert darstellt (nach 10 Sekunden will der ungeduldige Anwender spätestens wissen, was los
ist).
Jetzt richten wir für alle Programme eine eigene Abteilung in der Konfigurationsdatei ein:
[programmname]
sema=xxx
Wenn ein Programm wirklich nur lesend auf einen Datenbestand zugreift und kein anderes Programm auf den gleichen Datenbestand schreibend zugreift, so
können wir für dieses Programm den Wert "nosema" für "xxx" eintragen. In diesem Fall wird der Wächter nach Hause geschickt, und beliebig viele Instanzen der
tdbengine können das Programm gleichzeitig ausführen.
Alle anderen Programme die entweder lesend oder schreibend oder beides auf einen gemeinsamen Datenbestand zugreifen, erhalten alle einen gemeinsamen
Semaphoren.
Dazu ein Angenommen wir programmiern ein Gästebuch mit den drei Einzelprogrammen:
gaestebuch_lesen.prg
gaestebuch_schreiben.prg
gaestebuch_verwalten.prg
Alle diese Programme greifen auf Gästebuch-Datenbestand zu: Tabellen, Konfigurationsdateien usw., und zwar lesend (alle drei) sowie schreibend (die letzten
zwei). Damit sich diese nicht in Quere kommen, richten wir für alle drei einen Semaphor ein:
# Gästebuch-Abteilung
[gaestebuch_lesen]
sema=gaestebuch.sema
[gaestebuch_schreiben]
- 396 -
Betriebssystemfunktion
sema=gaestebuch.sema
[gaestebuch_verwalten]
sema=gaestebuch.sema
Hinweis: Mit dem #-Zeichen anfangende Zeilen werden als Kommentar interpretiert.
Jetzt ist dafür gesorgt, dass diese Programme nicht parallel ausgeführt werden, und damit wird wirksam ein Datendurcheinander vermieden.
42.5.1.1 SEMAPHOREN IM PROGRAMM
Zusätzlich zu den hier gezeigten Möglichkeiten gibt es noch zwei EASY-Funktionen, mit denen die Semaphoren noch wesentlich feiner bearbeitet werden
können:
WaitSema(Semaphor,TimeOut)
EndSema
Zum richtigen Einsatz dieser Funktionen ist eine Information ganz wesentlich: Jedes Programm kann zu einem Zeitpunkt maximal 10 Semaphoren
beschäftigen.
Oftmals ist es beispielsweise so, dass die eigentlichen Datenbankzugriffe nur sehr wenig Zeit beanspruchen, die "künsterlische Aufbereitung" hingegen sehr viel.
In Bruchteilen einer Sekunde ist ein Datensatz gefunden und gelesen. Jetzt kann eigentlich die Sperre aufgehoben werden, selbst wenn auf den Datensatz
mittels GetField() oder Subst() weiterhin zugegriffen wird.
Ganz prinzipiell kann gesagt werden, dass nach dem letzten lesenden oder scheibenden Zugriff dem Wächter mitgeteilt werden kann: "Junge, ich bin durch. Du
kannst jetzt den Nächsten 'reinlassen". Dazu dient die Funktion EndSema().
Hinweis: Etwas Ähnliches macht die tdbengine ganz automatisch, indem sie die CGI-Ausgaben zunächst puffert, dann den Semaphor freigibt, und jetzt erst die
Daten zum Webserver und damit zum Klienten überträgt. Diese Datenübertragung ist mit Sicherheit nicht kritisch.
Der Einsatz der Funktion WaitSema() ist schon wesentlich heikler, denn er muß unbedingt vor dem Öffnen der ersten Tabelle erfolgen. Denn bereits beim
Öffnen werden viele relavante Daten gelesen (im Gegensatz zum Schließen, wo keine Informationen mehr geschrieben werden).
Also nicht:
db:=OpenDB(...);
IFWaitSema('mysema',1000) THEN
WriteRec(db,...)
ELSE
- 397 -
Betriebssystemfunktion
ErrorMessage('Tabelle ist derzeit gesperrt')
END
Sondern:
IFWaitSema('mysema',5000) THEN
db:=OpenDB(...);
...
WriteRec(db,...);
...
EndSema
ELSE
ErrorMessage('Tabelle ist derzeit gesperrt')
END
42.5.1.2 LANGE AKTUALISIERUNGSLÄUFE
Zuletzt noch ein Beispiel aus der Praxis. Gerade im Internet gibt es viele Situationen, in denen eine permanente Aktualsierung der Datenbestände weder möglich
noch notwendig ist. Denken Sie einmal an die Suchmachinen. Bei den vielen Zugriffen wäre eine Sequentialiserung der Prozesse durch Semaphoren ein Unding.
Also lassen solche Programme zunächst einmal überhaupt keine schreibenden Zugriffe zu, sondern sammeln die neu eingegeben URLs erst einmal in einer
eigenen Tabelle. Dann, irgendwann in der Nacht, wenn die Last sinkt, wird eine Kopie des bisherigen Datenbestandes angelegt und in diesen werden die über
den Tag gesammelten Daten eingefügt. Dann werden diese und eventuell ältere URLs abgeklappert, die zugehörigen Seiten gelesen und die relevanten
Informationen in Suchstrukturen aufbereitet. Schließlich wird der ursprünglich Datenbestand durch den gerade aktualiserten ersetzt.
Insgesamt ist es nur eine einzige, recht kurze Sperre, die hier benötigt wird, nämlich beim Ersatz des Datenbestandes durch die aktualisierte Kopie.
Hinweis: Zusätzlich muss natürlich das Programm, das neue URLs in einer Hilfstabelle sammelt, saubere Sperren einrichten, aber die betreffen nicht die SuchAbfragen und spielen zeitlich auch keine Rolle.
Wenn in einem Datenbestand nur selten, dafür aber lange Reorganistionsaufgaben erfordert, so sollten Sie auf Semaphoren gänzlich verzichten (sema=nosema)
und während der Aktualisierung die lesenden Programme gänzlich abschalten. Das geht ganz einfach mit den Bordmitteln der tdbengine:
// Aktualiserung des Datenbestandes.
// Fogende Programme greifen lesend auf den Datenbestand zu:
// programm_1
// programm_2
// programm_3
// 1. CGI-Ausführung für diese Programme stoppen
- 398 -
Betriebssystemfunktion
VAR Konfig : STRING = 'tdbengine.ini'
SetIdent(Konfig,'programm_1.stopcgi','1')
SetIdent(Konfig,'programm_2.stopcgi','1')
SetIdent(Konfig,'programm_3.stopcgi','1')
// 2. Zehn Sekunden warten, BIS alle Programme beendet sind
Pause(1000)
// 3. Datenaktualsierung durchführen
db:=OpenDB(...)
...
CloseDB(db)
// 4. CGI-AUsführung für die anderen Programme wieder Zulassen
SetIdent(Konfig,'programm_1.stopcgi','0')
SetIdent(Konfig,'programm_2.stopcgi','0')
SetIdent(Konfig,'programm_3.stopcgi','0')
- 399 -
Betriebssystemfunktion
43.
TIPPS FÜR PHP-PROGRAMMIERER
PHP-Programmierer müssen beim Einsatz der tdbengine ein wenig umdenken:
tdbengine-Programme sind keine geparsten HTML-Seiten, sondern reine CGI-Programme, die selbst eine HTML-Seite erzeugen müssen. Zu diesem Zweck
können Sie einfache WRITE-Anweisung direkt an den Klienten senden oder (besser!) ein Template (HTML-Seite mit Platzhaltern) laden, dort Ersetzungen
durchführen und schließlich diese Seite an den Klienten (Browser) senden.
Aus diesem Grund ist es nicht nötig, für jede Anfrage bzw. jede Aktion ein eigenes Programm zu schreiben. Die tdbengine fördert vielmehr, dass die gesamte
Funktionalität beispielsweise für ein Pressemitteilungs-System in nur einem Programm steckt. Die einzelnen Funktionen werden über GET- und/oder POSTParameter übergeben und von einem zentralen Dispatcher ausgewertet. So schaut in vielen tdbengine-Programmen die Hauptfunktion so aus:
PROCEDUREMain
VAR command:=GetQueryString('command')
IF command='suchen' THEN ...
ELSIF command='anmelden' THEN ...
ELSIF command='abmelden' THEN ...
ELSIF ...
...
ELSE ... // Startseite
END
ENDPROC
Die integrierte Datenbank arbeitet wesentlich direkter mit Tabellen als der Anschluß an eine externe SQL-Datenbank. Das schaut für PHP-Leute sicherlich erst
einmal fremd aus, wahrscheinlich sogar fremder als die Pascal-ähnliche Syntax der Programmiersprache EASY.
Hier ein kleines Codefragment, das eine Query ($PLZ like "8????") in einer Adressentabelle absetzt und die gefundenen Reihen ausgibt:
VAR db : INTEGER = OpenDB('database/adresen.dat') //Tabelle (zum Lesen) öffnen
VAR nn : INTEGER = FindAndMark(db,'$PLZ LIKE "8????"') // Suchen
IF nn=0 THEN //Kein Treffer
CGIWriteLn('Leider nichts gefunden')
ELSE // Die Tabellenzeilen der gefundenen Datensätze sind IN der Markierungsliste
CGIWriteLn(Str(nn)+' Datensätze gefunden.') // Erst einmal eine Meldung
VAR x : INTEGER = FirstMark(db) // An den Anfang der Markierungsliste
WHILE x>0 DO // Die gesamte Markierungsliste durchgehen
ReadRec(db,x) // Zeile holen
- 400 -
Betriebssystemfunktion
CGIWriteLn(GetField(db,'Name')) // Spalten ausgeben
CGIWriteLn(GetField(db,'Strasse'))
...
x:=NextMark(db,x)
END
END
Freilich kann man sich auch eine Ergebnis-Tabelle (in Form eines String-Arrays) zusammenstellen lassen, und freilich gibt es auch die Möglichkeit, die tdbengine
als reinen SQL-Server zu verwenden (=aktuelles Projekt der TDB GmbH in Schwabach) - der direkte Tabellenzugriff ist allerdings um Längen performanter...
43.1.1
Locking Daemon
43.1.1.1 BESCHREIBUNG
Bisher konnten konkurrierende Schreibzugriffe entweder über die System-Semaphore c gi synchronisiert werden, oder es lag in der Verantwortung des
Entwicklers, bei jedem Schreibzugriff eine Semaphore einzurichten - Besonders bei Entwicklung im Team fehlerträchtig. Der locking daemon ermöglicht es nun,
diese Synchronisation an zentraler Stelle automatisch durchzuführen. Beim Start baut die tdbengine eine TCP/IP-Verbindung zu dem im Hintergrund laufenden
locking daemon auf. Bei jeder Aktion, die eine Synchronisation erfordert (s.u.), kommuniziert die tdbengine über diese Verbindung mit dem locking daemon und
fordert Sperren an oder gibt diese frei. Dies kann man in der Logdatei beobachten.
43.1.1.2 INSTALLATION
Für die Verwendung des locking daemons ist das Programm t db_l oc k d sowie die dazu gehörige tdbengine erforderlich. Diese ist an einem an die
Versionsnummer angehängten - net erkennbar und bisher nur für Linux erhältlich.
Der daemon muß, wie der Name schon andeutet, als Hintergrundprozess auf der Konsole gestartet werden. Hierzu geben Sie auf der Linux-Konsole folgenden
Befehl ein:
nohup ./tdb_lockd > tdb_lockd.log
Danach kann die Konsole geschlossen werden, der daemon läuft im Hintergrund weiter.
Es wird eine Datei tdb_lockd.ini angelegt, in der die Verwaltungsinformationen abgelegt werden.
Die log-Datei kann man mit
tail -f tdb_lockd.log mitlaufen lassen.
Beendet wird der daemon mit
killall tdb_lockd
43.1.1.3 VERWENDUNG
Für das locking sind die neu aktivierten alten Befehle Lock, Unlock, EditOn, EditOff, GetSession, DelSession, GetSessionIdent und SetSessionIdent zuständig
- 401 -
Betriebssystemfunktion
43.1.1.4 TABELLENSPERREN
Wenn eine Tabelle unter Verwendung des locking daemons geöffnet wird gilt:
Zu einem Zeitpunkt können entweder genau ein schreibender oder beliebig viele lesende Zugriffe auf eine Tabelle erfolgen.
Die Beschränkung auf genau einen schreibenden Zugriff bedarf der Erläuterung. Sie hängt in erster Linie mit der Indizierung einer Tabelle zusammen. Es wäre
schließlich denkbar, dass der Anwender A den Datensatz X und der Anwender B den Datensatz Y ohne weitere Komplikationen gleichzeitig verändern können.
Für die Tabelle selbst ist dies auch richtig. Eine einzige Änderung einer Indexinformation hingegen kann zu einer völligen Umstrukturierung der gesamten
Indexdatei führen. Deshalb dürfen während der Aktualisierung der Indexe keine weiteren schreibenden Operationen in der Tabelle stattfinden. Bezüglich der
Indizierung ist demnach jeder schreibende Zugriff eine die gesamte Tabelle betreffende Operation. Der locking daemon unterscheidet nicht zwischen indizierten
und nichtindizierten Tabellen, obwohl letztere in der Praxis kaum vorkommen.
Um gleich hier einem möglichen Mißverständnis vorzubeugen: Die obige Regel gilt natürlich nur für den eigentlichen Übertragungsvorgang von Informationen
(also Datensätzen) vom Rechner auf das externe Speichermedium. Die Dateneingabe selbst, also das Ausfüllen von Formularen ist davon selbstverständlich
nicht betroffen. Es können beliebig viele Anwender gleichzeitig Daten in Formulare eingeben. Erst wenn die Eingabe eines Satzes abgeschlossen ist und der
Satz "geschrieben" werden muss, tritt die Regel in Kraft.
Damit die Regel immer eingehalten wird, setzt der locking daemon bei lesenden und schreibenden Zugriffen auf eine Tabelle sogenannte Sperren ein. Bei einem
schreibenden Zugriff ist die Sperre total (Totalsperre), es werden also während dieser Zeit überhaupt keine anderen Zugriffe auf die Tabelle zugelassen. Beim
lesenden Zugriff hingegen wird die Datei nur für schreibende Zugriffe anderer Anwender gesperrt (Schreibsperre).
43.1.1.5 SATZ-SPERREN
Wenn Tabellen von Anwendern interaktiv bearbeitet werden sollen gilt:
Befindet sich ein Datensatz bei einem Anwender in Bearbeitung, so darf dieser Datensatz von einem anderen Anwender nicht gleichzeitig bearbeitet oder
gelöscht werden.
Während der Bearbeitung durch den Anwender sollte also nicht die gesamte Tabelle gesperrt, sondern nur derjenige Datensatz, der davon betroffen ist (SatzSperre). Diese Sperre betrifft auch nicht alle Operationen auf diesem Datensatz (z.B. kann er nach wie vor gelesen werden), sondern nur das Editieren oder
Löschen dieses Satzes durch einen weiteren Anwender.
Zur Einhaltung muß das Programm entsprechende Sperren einrichten. Für die Sperren gilt folgende Grundregel:
Schreibende Zugriffe führen zu Totalsperren,
lesende Zugriffe führen zu Schreibsperren.
Damit die gemeinsame Arbeit mehrerer Anwender mit einer Tabelle nicht durch übertrieben lange Pausen behindert
wird, sollten sämtliche Sperren nur so lange wie unbedingt nötig aufrecht gehalten werden.
43.1.1.6 AUTOMATISCHE SPERREN
Hier eine Aufstellungaller Tabellen-Funktionen, die automatische Sperreneinrichten:
- 402 -
Betriebssystemfunktion
Funktion
Sperren
ReadRec
Schreibsperre
WriteRec
Totalsperre, Satzsperre (wenn x<=FileSize)
DelRec
Satzsperre, Totalsperre
FindRec
Schreibsperre
GenInd
Totalsperre
LinkBlob
Totalsperre, Blobsperre
ReadMemo
Totalsperre, Memosperre
CopyMemo
Schreibsperre, Memosperre
__________________________________________________________________________________
__________________________________________________________________________________
Hg: Bis hierher
43.1.2
Funktion Choice
A
Funktion choice()
Kurzbeschreibung
Beschreibung
a
Einen Wert aus einer Liste auswählen Syntax Choice(Zahl: INTEGER;Ausdruck1,Ausdruck2,..., AusdruckN): Real/String
Parameter Ergebnis
Die Funktion liefert den Zahl-ten Ausdruck zurück. Wenn Zahl nicht im Bereich von 1..N liegt, liefert die Funktion als Ergebnis
immer den letzten Ausdruck AusdruckN.
Je nach Typ des Ausdrucks liefert die Funktion entweder einen String oder einen Zahltyp zurück
Erst wird der numerische Ausdruck Zahl berechnet (ergibt n). Im Anschluß daran wird der n-te Ausdruck berechnet und als
Ergebnis dieser Funktion zurückgeliefert. Falls kein n-ter Ausdruck vorhanden ist, wird der letzte Ausdruck der Reihe berechnet.
- 403 -
Betriebssystemfunktion
43.1.2.1 Verwendung bei Auswahlfeldern
Sehr praktisch ist diese Funktion beispielsweise bei der Auswertung von Auswahlfeldem.
Intern haben Auswahlfelder den Wert 0, wenn sie ganz leer sind, ansonsten eine 1, wenn der erste Wert gewählt ist, eine 2 beim zweiten Wert usw. Im folgenden
Beispiel ist "Geschlecht" ein Auswahlfeld mit den Konstanten "weiblich", "männlich" und "unbekannt".
Choice(getrfield(A,“Geschlecht“),"Sehr geehrte Frau "+getfield(A,“Name“),"Sehr geehrter Herr "+ getfield(A,“Name“),"Sehr geehrte Damen und Herren")
43.1.2.2 Bedingter Funktionsaufruf
Falls eine Bedingung zutrifft soll eine Anweisung ausgeführt werden:
Choice(Sel(GetField(E,"AutoID_Termin")=GetField(E,"AutoID_Termin2")),SetRField(E,"AutoID_Termin2",0),"")
43.1.2.3 Bekannte Bugs:
Der Compiler erkennt nicht, wenn eine Klammer zu früh geschlossen wurde.
In der Konsequenz werden die Anweisungen nach der geschlossenen Klammer nicht mehr ausgeführt und damit immer die letzte Anweisung vor der
geschlossenen Klammer
Choice(Sel(<Bedingung>),SetRField(E,"Betrag",0)), SetRField(E,"Betrag",4711))
Die Zeile wird im Compiler nicht beanstandet, aber es wird immer nur SetRField(E,"Betrag",0)) ausgeführt.
Die Anweisung SetRField(E,"Betrag",4711) gelangt dabei nie zur Ausführung.
- 404 -
Betriebssystemfunktion
43.2
Lektion 6: Die Datenbank der tdbengine
Terminologie
Ein Beispiel
Das ADL-System
Die Strukturdefinition der Tabellen
Indizes
Datenbank-Zugriff
Performance
Aktualität
Flexibilität
Praxis
Öffnen und Schliessen von Tabellen
Lesen von Datensätzen
Feldzugriffe
Markierungen
Adressen-Suchmaschine
Aufgaben
Nachdem wir nun die Grundzüge der Programmierung in EASY und die wichtigsten allgemeinen Funktionen aus der Standardbibliothek kennengelernt haben,
geht es diesmal um die Arbeit mit Datenbanken.
43.2.1
Terminologie
Der Zugriff auf einzelne Spalten erfolgt über Namen, die als Feldbezeichner oder (Feld-)Label bezeichnet werden. Der Zugriff auf die einzelnen Zeilen einer
Tabelle erfolgt entweder über eine Satznummer (die Position des Satzes innerhalb der einer Tabelle zugrunde Datei) oder der Angabe einer Ausprägung eines
bestimmten Feldinhaltes.
Die Anordnung der Spalten zusammen mit deren Typisierung (= welcher Inhalt wie Zeichenette, Zahl, Datum etc. kann aufgenommen werden) bildet die Struktur einer Tabelle.
Mehrere Tabellen können miteinander verknüpft werden, indem die Inhalte eines Feldes oder mehrerer Felder der einen Tabelle in eine Abhängigkeit zu entsprechenden Feldern
der anderen Tabelle gesetzt werden. Mengenlogisch gesehen handelt es sich dabei um Teilmengen aus dem Kreuzprodukt der beiden Tabellen, also um Relationen.
Eine oder mehrere Tabellen bilden eine Datenbank.
Ein Beispiel
- 405 -
Betriebssystemfunktion
Nach dieser recht trockenen Einführung wollen wir die Begriffe an einem Beispiel ansehen. Wir wollen dazu die klassische Firmendatenbank verwenden, weil sie
jeder kennt und wahrscheinlich auch verwenden kann.
Wir wollen das Beispiel insoweit vereinfachen, als wir nur Firmen mit einem Firmensitz betrachten. Unsere Datenbank soll also solche Firmen speichern, sowie zu jeder Firma
eine beliebige Anzahl von Kontaktpersonen. Um dem zweiten Kriterium »beliebige Anzahl von Kontaktpersonen« gerecht werden zu können, benötigen wir zwei Tabellen:
Firmendaten und Kontaktpersonen.
Im nächsten Schritt gilt es, die Strukturen der Tabellen festzulegen, also die Spalten genau zu definieren: Firmen:
Firmenname
Strasse
Land
PLZ
Ort
Telefon Zentrale
Fax Zentrale
Allgemeine E-Mail
Zeichenkette mit max. 60 Zeichen
Zeichenkette mit max. 40 Zeichen
Zeichenkette mit max. 10 Zeichen
Zeichenkette mit max. 20 Zeichen
Zeichenkette mit max. 40 Zeichen
Zeichenkette mit max. 20 Zeichen
Zeichenkette mit max. 20 Zeichen
Zeichenkette mit max. 40 Zeichen
Kontaktpersonen:
Titel
Vorname
Name
Position
Funktion
Telefon
Telefax
E-Mail
Zeichenkette mit max. 20 Zeichen
Zeichenkette mit max. 30 Zeichen
Zeichenkette mit max. 30 Zeichen
Zeichenkette mit max. 40 Zeichen
Zeichenkette mit max. 40 Zeichen
Zeicheknette mit max. 20 Zeichen
Zeichenkette mit max. 20 Zeichen
Zeichenkette mit max. 40 Zeichen
Jetzt kommen wir zu einem typischen Problem relationaler Datenbanken:
Die Tabelle »Kontaktpersonen« benötigt ein Feld, in dem gespeichert wird, zu welcher Firma eine Kontaktperson gehört (eine sogenannte Referenz auf die Firmen-Tabelle).
- 406 -
Betriebssystemfunktion
Der erste Gedanke, hier einfach den Firmennamen mit aufzunehmen, erweist sich auf den zweiten Blick als nicht optimal: Können wir ausschließen, dass es nicht zwei Firmen
mit absolut gleichem Namen gibt? Sie werden sagen, dass das sehr unwahrscheinlich ist. Aber Datenbanken benötigen keine Wahrscheinlichkeiten, sondern Sicherheit.
Nun könnte man vielleicht auf die Idee verfallen, sämtliche Felder der Firmen-Tabelle in die Tabelle für die Kontaktpersonen zu übernehmen. Damit würden freilich viel zu viele
redundante Informationen gespeichert, und Redundanzen sollten (im Sinne einer einfachen und effektiven Datenhaltung) auf einem sinnvollen Niveau bleiben. Die systematische
Entfernung von Redundanzen aus einem Datenbanksystem durch Bildung von Relationen nennt man übrigens »Normalisieren« Als Anker für eine Referenz bietet sich also nur
ein Feld an, das einen Datensatz (eine Zeile) innerhalb einer Tabelle eindeutig zuordnet. Ein solches Feld hat unsere Firmen-Tabelle (noch) nicht. Wir müssen also deren Struktur
um ein solches Feld erweitern. Wenn es darum geht, Eindeutigkeit zu schaffen, bietet sich sofort eine Nummerierung an. Und damit wir nicht selbst jedes mal eine Nummer
vergeben müssen (inkl. Prüfung auf Eindeutigkeit, die besonders schwierig ist, wenn im Netzwerkbetrieb mehrere Anwender gleichzeitig mit der Tabelle arbeiten), überlassen
wir diese Arbeit dem Datenbank-System.
Nahezu jede Datenbank bietet eine solchen Feldtyp an, der meist als AUTO-INCREMENT bezeichnet wird. Es liefert ein Feld, das die eindeutige Identifizierung
eines Satzes erlaubt. Solche Felder werden als (Primär-)Schlüsselfelder bezeichnet. Felder, die eine Referenz auf einen Primärschlüssel einer anderen Tabelle
speichern, werden auch als Fremdschlüssel bezeichnet. Die Tabelle für die Kontaktpersonen enthält dann demnach noch ein Feld, in dem der Firmenschlüssel
gespeichert wird, wodurch die eindeutige Zuordnung einer Kontaktperson zu einer Firma hergestellt werden kann. Die tdbengine speichert AUTO-INCREMENTFelder in einer 32-Bit-Zahl (NUMBER,4) ab, so dass wir diesen Typ auch für die Refernz verwenden müssen: Firmen:
…
Firmenschlüssen (AUTO-INCREMENT)
Kontaktpersonen:
...
Firmenreferenz (32-Bit-Zahl)
43.2.2
Das ADL-System
Nachdem eine solche Tabellenverknüpfung (eine referenzielle Zuordnung einer Tabelle zu einem Schlüssel einer anderen) so häufig verwendet wird, bietet die
tdbengine für genau diesen Fall eine ganz wesentliche Vereinfachung an: den Feldtyp »LINK«. In einem Fortgeschrittenen-Kurs werden wir das ADL(=Automatic
Data Link)-System der tdbengine genauer vorstellen und diskutieren.
Die Strukturdefinition der Tabellen
Wir können nun die Strukturen der beiden Tabellen als Strukturdefinition im tdbengine-Stil angeben:
Firmen:
[STRUCTURE]
field_1=Firmenname,STRING,60
field_2=Strasse,STRING,40
field_3=Land,STRING,10
field_4=PLZ,STRING,20
field_5=Ort,STRING,10
field_6=Telefon,STRING,20
field_7=Telefax,STRING,20
- 407 -
Betriebssystemfunktion
field_8=EMail,STRING,40
field_9=Firmennummer,AUTO
Kontaktpersonen:
[STRUCTURE]
field_1=Titel,STRING,20
field_2=Vorname,STRING,30
field_3=Name,STRING,30
field_4=Position,STRING,40
field_5=Funktion,STRING,40
field_6=Telefon,STRING,20
field_7=Telefax,STRING,20
field_8=EMai,STRING,40
field_9=Firmenreferenz,NUMBER,4
43.2.3
Indizes
Wenn Sie die Firmenstruktur in unser »Database Developement Kit« eingeben und die Tabelle »Fimen« generieren wollen, erhalten sie die Fehlermeldung »kein
ID-Index festgelegt«. Das wirft gleich zwei Fragen auf: Was ist ein Index im Allgemeinen? Und was ist ein ID-Index im Speziellen?
Bevor wir die Frage beantworten, was ein Index ist, wollen wir seine Funktion in einem Datenbanksystem beschreiben:
Ein Index erlaubt einen sehr schnellen Zugriff auf einzelne Datensätze einer Tabelle bezüglich ganz bestimmter Felder. Dazu werden die Inhalte dieser Felder aus der Tabelle
extrahiert und zusammen mit einer Referenz auf den zugehörigen Datensatz in einer speziellen Suchstruktur zur Verfügung gestellt. Das klingt kompliziert und ist in der
technischen Ausführung noch viel komplizierter.
Doch schauen wir zunächst wieder ein Beispiel an. Angenommen, unsere Firmentabelle enthält nun viele Tausend Einträge (Zeilen, Datensätze). Wir wollen nun die Firma mit
dem Firmennamen »TDB GmbH« suchen (Datenbank-Deutsch: selektieren). Dazu können wir Datensatz für Datensatz lesen und prüfen, ob im Feld »Firmenname« die
Zeichenkette »TDB GmbH« steht. Eine solche Suche wird als »sequentiell« bezeichnet. Leider dauert das Lesen der Datensätze und deren Überprüfung immer eine gewisse Zeit,
so dass die Suche nicht besonders effizient ist und zudem die Dauer linear mit der Größe der Tabelle wächst.
Wenn wir einen Index haben, der das Feld »Firmenname« berücksichtigt, so liefert uns der Index eine Referenz zu dem Datensatz, in dem die gewünschte Information steht, und
wir müssen nur diesen lesen und nichts mehr prüfen. Damit ein Index auf diese Art und Weise effizient arbeiten kann, werden die extrahierten Informationen in ganz speziellen
Suchstrukturen angelegt (meist baumartige Gebilde) die einen Zugriff erlauben, der nur noch logarithmisch mit der Tabellengröße wächst (grob gesagt: ab einer bestimmten
Tabellengröße nahezu konstant bleibt). Für Eingeweihte nur soviel: Die tdbengine legt einen Index als externen B-Tree an.
Die tdbengine erlaubt in der derzeitigen Version die Anlage von bis zu 15 Indizes pro Tabelle. Einige Indizes werden automatisch angelegt, so beispielsweise ein Index über ein
AUTO-INCREMENT-Feld. Weitere können jederzeit angelegt und wieder entfernt werden. Die Frage, was denn nun ein ID-Index ist, kann jetzt relativ einfach beantwortet
- 408 -
Betriebssystemfunktion
werden: Ein ID-Index ist ein vom Anwender definierter Index, der den Hauptzugriff auf die Tabelle berücksichtigen soll, und der vom System eine besondere Pflege erfährt. Die
besondere Pflege besteht darin, dass dieser Index automatisch immer wieder erzeugt wird, auch wenn die zugehörige Indexdatei (aus irgendeinem Grund) einmal verlorengeht.
Eine weitere Rolle spielt der ID-Index im Zusammenhang mit dem bereits erwähnten ADL-System, das jedoch in diesem Kurs nicht besprochen wird. Nachdem der
hauptsächliche Zugriff auf unsere Firmentabelle sicherlich über das Feld »Firmenname« erfolgt, werden wir dieses zum ID-Index verwenden.
Die Indizes haben in der Strukturdefinition für die tdbengine eine eigene Abteilung:
[INDEX]
Der ID-Index wird hier folgendermaßen angeben:
id=Indexbeschreibung
Eine »Indexbeschreibung« ist wiederum eine relativ komplexe Sache, weil die tdbengine nicht nur einfache Indizes über ein Feld kennt, sondern zusätzlich hierarchische und
berechnete Indizes verarbeiten kann. Wir wollen hier nur den allereinfachsten Fall betrachten, dass der Index über genau ein Feld angelegt wird. In diesem Fall besteht die
Indexbeschreibung genau aus dem entsprechenden Feldbezeichner:
[INDEX]
id=Firmenname
Wir wollen hier also nochmals festhalten:
Ein Index wird über Spalten (Felder) einer Tabelle gebildet.
Er erlaubt eine besonders schnelle Suche nach diesen Spalten.
Zudem kann über diesen Index geordnet (sortiert) auf die Tabelle zugegriffen werden.
Datenbank-Zugriff
Wenn wir eine Anfrage an eine Datenbank stellen - etwa: »Gib mir alle Firmen aus München« - so gibt es für das Ergebnis zwei grundsätzlich verschiedene
Strategien:
das Datenbank-System liefert eine komplette (Ergebnis-)Tabelle
das Datenbank-System liefert eine Menge von Verweisen auf Zeilen (Datensätze)
Die tdbengine arbeitet nach der zweiten Methode. Diese ist zwar komplizierter (vor allem bei der Bildung von sog. »Joins« - das sind Abfragen über mehrere verknüpfte
Tabellen) und schwieriger zu verstehen, bietet aber auch einige Vorteile, die wir hier kurz anreissen wollen:
- 409 -
Betriebssystemfunktion
43.2.4
Performance
Im Normalfall müssen viel weniger Daten übertragen werden, denn die Zeilen werden werden nur bei echtem Bedarf vom DB-Server an den Klienten übertragen. Oftmals
interessiert nicht die gesamte Ergebnistabelle, sondern nur ein fortlaufender Ausschnitt daraus, weil das Ergebnis beispielsweise seitenweise in einem Browser angezeigt wird.
Zudem muss der DB-Server (also die tdbengine) in vielen Fällen die Tabelle nicht einmal lesen, um das Ergebnis bereitstellen zu können, weil beispielsweise die Informationen
in einem Index ausreichen.
43.2.5
Aktualität
Das dynamische Verfahren der tdbengine bietet die sogenannte Zugriffs-Aktualität. Ein Datensatz wird erst bei Bedarf vom Server aus der originalen Tabelle übertragen. In einer
Datenbank, die häufigen Änderungen unterliegt (beispielsweise Zugriffszähler auf gut besuchte Web-Seiten) kann sich der Inhalt eines Datensatzes durchaus zwischen Selektion
und Zugriff ändern. Somit steht eine möglichst hohe Aktualität zur Verfügung.
Freilich bietet das System auch den Nachteil, dass der aktuell gelesene Satz die ursprüngliche Selektion nicht mehr erfüllt. Wenn solche Fälle ausgeschlossen werden müssen,
kann das durch eine entsprechende Sperre der Tabelle realisiert werden.
43.2.6
Flexibilität
Die Klienten-Applikation kann recht geschmeidig auf ein Abfragergebnis reagieren. Es kann diese beispielsweise verwerfen, bevor auch nur ein Datensatz gelesen werden muss.
Das ganze System arbeitet recht nahe an der Hardware, mit allen Vor- und Nachteilen. Bei einem Abfrageergebnis handelt es sich um physikale Satznummern, also FilePositionen. Damit kann man unglaubliche Dinge tun - gute und schlechte. Der Hauptvorteil der Zugriffs-Strategie der tdbengine liegt darin, dass man mit ihr alle anderen
»nachbauen« kann. So kann eine EASY-Prozedur geschrieben werden, die das Ergebnis ein Abrage in Form einer Ergebnistabelle zur Verfügung stellt. In einer der folgenden
Versionen wird eine solche Prozedur auch in die Systembibliothek aufgenommen werden.
43.2.7
Praxis
Nach so viel Theorie wollen wir nun die wichtigsten Funktionen der Standardbibliothek für Tabellen vorstellen. Da wären zunächst die Funktionen, die eine ganze
Tabelle betreffen:
MakeDB()
generiert eine neue Tabelle
DelDB()
löschte eine bestehende Tabelle
RenDB()
benennt eine bestehenede Tabelle um
OpenDB()
öffnet eine bestehende Tabelle zur Bearbeitung
CloseDB()
schließt eine geöffnete Tabelle
Die ersten drei Parameter sind bei all diesen Funktionen (mit Ausnahme von CloseDB) gleich:
- 410 -
Betriebssystemfunktion
Pfad zur Tabelle (Dateiname)
Passwort
Verschlüsselungscode
Hinweis: Ein Verschlüsselungscode wird nur aktiviert, wenn auch ein Passwort angegeben wird. Wir werden in diesem Kurs grundsätzlich mit ungeschützen
Tabellen arbeiten, also weder Paswort noch Code angeben. Ab dem vierten Parameter unterscheiden sich die Funktionen:
MakeDB()
Pfad zu einer Strukturdefinition
Pfad zu einer Import-Quelle (optional)
DelDB (Kein weiterer Parameter)
RenDB
Neuer Name
OpenDB
43.2.8
Berechtigungen
Wir wollen die Funktionen MakeDB(), DelDB() und RenDB() hier nicht weiter besprechen, da Anlage und Umstrukturierung von Tabellen mit dem »Database Developement
Kit« erledigt werden.
Hinweis: Von MakeDB() gibt es noch zwei Sonderformen: GenList() und GenRel(), die im Zusammenhang mit der Volltextindizierung die Arbeit vereinfachen.
Öffnen und Schließen von Tabellen
Bevor wir in einem EASY-Programm auf die Inhalte einer Tabelle zugreifen können, müssen wir diese öffnen. Dafür steht, wie bereits angemerkt, die Funktion
OpenDB() zur Verfügung: Die einfachste Form ist
OpenDB(Pfad_zur_Tabelle : STRING) : INTEGER
Das Ergebnis dieser Funktion ist entweder 0, wenn die Tabelle nicht geöffnet werden konnte, oder eine Zahl, die in der Folge die Verbindung zur Tabelle repräsentiert. Diese
Zahl, den sogenannten »Tabellen-Handle«, speichern wir grundsätzlich in einer INTEGER-Variablen. Falls das Ergebnis 0 ist, wird ein Laufzeitfehler ausgelöst, der (wie in der
letzten Folge dieses Kurses besprochen) abgefangen werden kann. Die Fehlerursache kann dann mit TDB_LastError etc. genauer untersucht werden (und steht auch in
tdbengine/bin/error.log). Somit schaut die Standardsequenz in einem Programm so aus:
VAR db : INTEGER
SetPara('ec 1')
- 411 -
Betriebssystemfunktion
db:=OpenDB('database/adressen.dat')
IF db=0 THEN
// Meldung, dass Tabelle NICHT verfügbar ist
ELSE
// hier kann mit der Tabelle gearbeitet werden
CloseDB(db)
END
Wie haben hier gleich CloseDB() verwendet. Diese Funkion schliesst eine Tabelle. Mit dieser einfachen Version von OpenDB() wird eine Tabelle ohne Paswort und ohne
Verschlüsselungscode nur zum Lesen geöffnet.
43.2.9
Lesen von Datensätzen
Jeder Tabellenhandle verfügt über genau einen Satzpuffer, also einen Speicherbereich, der genau einen Datensatz aus der Tabelle speichern kann. Der Zugriff
auf die einzelnen Felder erfolgt genau über diesen Satzpuffer.
Doch wie gelangt nun der Inhalt einer Tabellenzeile in den Satzpuffer? Dafür gibt es die Funktion ReadRec():
ReadRec(Tabellenhandle : INTEGER; Zeile : INTEGER) : INTEGER
Diese Funktion überträgt (wenn alles gutgeht) die angegebene Zeile in den Satzpuffer. Das Funktionsergebnis ist entweder die Zeile (>=0) oder ein negativer Fehlercode. Auch
hier wird ein Laufzeitfehler ausgelöst, wenn irgendetwas nicht richtig ist (weil zum Beispiel die angegebene Zeile garnicht existiert). Die Zeile bechreibt die physikalische
Position innerhalb der Tabelle. Die größte mögliche Zahl für »Zeile« erhalten Sie mit der Funktion FileSize(), die die Gesamtzahl der Zeilen in der Tabelle liefert:
FileSize(Tabellenhandle : INTEGER) : INTEGER
Wir wollen jetzt einmal ein kleines Programmfragment schreiben, das eine komplette Tabelle durchgeht, also sämtliche Zeilen in den Satzpuffer eines Tabellenhandle überträgt:
VAR db, anzahl_zeilen, zeile : INTEGER
db:=OpenDB('database/adressen.dat')
anzahl_zeilen:=FileSize(db)
zeile:=0 // eigentlich überflüssig, weil mit 0 initialisiert
WHILE zeile:=zeile+1<=anzahl_zeilen DO //ODER zeile++<=anzahl_zeilen
ReadRec(db,zeile)
END
- 412 -
Betriebssystemfunktion
43.2.10
Feldzugriffe
Schön, jetzt haben wir eine ganze Tabelle gelesen. Aber gesehen haben wir davon nichts. Wie bereits gesagt, überträgt ReadRec() eine Zeile in den Satzpuffer.
Auf den Satzpuffer können wir mit den sogenannten Feld-Funktionen zugreifen.
EASY bietet u.a. folgende:
GetField() liefern den Inhalt eines eines Feldes aus dem Satzpuffer
SetField() schreibt einen String in ein Feld des Satzpuffers
Der erste Parameter dieser Funktionen ist wiederum der Tabellenhandle. Der zweite Parameter kann entweder die Feldnummer (als Zahl) oder der Feldname (als STRING) sein.
Bei SetField() kommt als zusätzlicher Parameter der neue Wert des Feldes (als STRING) hinzu.
Wie wollen zunächst nur GetField() betrachten:
GetField(Tabellenhandle : REAL; Feldnummer : REAL) : STRING
GetField(Tabellenhandle : REAL; Feldbezeichner : STRING) : STRING
Hinweis: Da sämtliche Feldfunktionen die Wahl zwischen Feldnummer und Bezeichner lassen, wollen wir in der Zukunft an dieser Stelle nur noch »Feld« schreiben. Der Zugriff
über den Feldbezeichner ist zwar minimal langsamer als über die Feldnummer, liefert aber auch dann noch richtige Resultate, wenn die Tabelle so umstrukturiert worden ist, dass
sich die Feldnummern verändert haben. Deshalb wollen wir in diesem Kurs vorrangig mit Feldbezeichnern arbeiten.
Zum letzten Mal erfolgt hier der Hinweis, dass auch diese Funktion (wie alle Tabellen- und Feld-Funktionen) einen Laufzeitfehler auslösen, wenn illegale Parameter übergeben
werden oder die Funktion aus anderen Gründen nicht ausgeführt werden konnte.
Damit können wir schon unser erstes kleines CGI-Program schreiben, das mit einer Tabelle arbeitet. Voraussetzung ist, dass Sie die Übungen im zweiten Teil unseres Kurses
durchgeführt haben und somit die Tabelle »adressen.dat« im Verzeichnis »tdbengine/database« (mit ein paar Einträgen) existiert. Andernfalls holen Sie das bitte jetzt gleich nach,
damit das Folgende so richtig Spaß macht.
MODULE datenbanktest_1.mod
PROCEDUREMain
VARDEF db, anzahl_zeilen, zeile : INTEGER
CGIWriteLn('content-type: text/html')
CGIWriteLn('')
CGIWriteLn('<html><body bgcolor="white">')
CGIWriteLn('<h2>Tabellen Auswertung</h2>')
- 413 -
Betriebssystemfunktion
CGIWrite('<pre>')
SetPara('ec 1')
IF db:=OpenDB('database/adressen.dat')=0
THENCGIWriteLn('Tabelle adressen.dat NICHT gefunden.')
ELSE
anzahl_zeilen:=FileSize(db); zeile:=0
WHILE zeile:=zeile+1<=anzahl_zeilen DO
ReadRec(db,zeile)
CGIWriteLn('Name: '+ToHtml(GetField(db,'Name')))
CGIWriteLn('Vorname: '+ToHtml(GetField(db,'Vorname')))
CGIWriteLn('Strasse: '+ToHtml(GetField(db,'Strasse')))
CGIWriteLn('PLZ: '+ToHtml(GetField(db,'PLZ')))
CGIWriteLn('Ort: '+ToHtml(GetField(db,'Ort')))
CGIWriteLn('---------------')
END
CloseDB(db)
END
CGIWriteLn('</body></html>')
ENDPROC
Ein Tipp: Starten Sie das Program Developement Kit und übertragen Sie den Programmtext mittels Copy/Paste in das Text-Fenster. Den Programmnamen übernehmen Sie aus
der MODULE-Zeile. So sparen Sie sich viel fehleranfällige Tipparbeit.
Als nächstes wollen wie eine E-Mail-Liste aus unserer Adressdatenbank erzeugen. Freilich sollen hier nur solche Sätze angezeigt werden, wo auch eine E-Mail-Adresse
eingetragen wurde.
MODULE datenbanktest_2.mod
PROCEDUREMain
VARDEF db, anzahl_zeilen, zeile : INTEGER
VARDEF email : STRING
CGIWriteLn('content-type: text/html')
CGIWriteLn('')
SetPara('ec 1')
IFOpenDB('database/adressen.dat')=0
THENCGIWriteLn('Tabelle adressen.dat NICHT gefunden.')
ELSE
anzahl_zeilen:=FileSize(db); zeile:=0
WHILE zeile:=zeile+1<=anzahl_zeilen DO
- 414 -
Betriebssystemfunktion
ReadRec(db,zeile)
IF email:=GetField(db,'Email') THEN
CGIWrite('<a href="mailto:'+email+'">')
CGIWrite(ToHTML(GetField(db,'Vorname')))
CGIWrite(' ')
CGIWrite(ToHTML(GetField(db,'Name')))
CGIWriteLn('</a><br>')
END
END
CloseDB(db)
END
CGIWriteLn('</body></html>')
ENDPROC
Markierungen
Neben dem oben angesprochenen Satzpuffer bietet ein Tabellenhandle auch noch den Zugriff auf eine beliebig große Liste von Satznummern. In unserem
Sprachgebrauch heisst diese Liste auch Markierungsliste. Folgende Basisfunktionen stehen für diese Liste zur Verfügung:
SetMark()
hängt eine Satznummer an das Ende der Liste
DelMark()
löscht eine Satznumer aus der Liste
DelMarks()
löscht die gesamte Liste
IsMark()
prüft, ob eine Satznummer in der List ist
FirstMark()
liefert den Anfang der Liste
NextMark()
liefert die nächste Satznummer aus der Liste
Revmarks()
invertiert die Markierungsliste
SortMark()
sortiert die Markierungliste
FindAndMark()
erzeugt eine Markierungsliste bzgl. einer Selektion
NMarks()
liefert die Anzahl der Datensätze in der Markierungsliste
Es würde zu weit führen, alle diese Funktionen hier zu besprechen. Wir werden nur folgende genauer unter die Lupe nehmen:
FindAndMark()
SortMark()
FirstMark()
NextMark()
- 415 -
Betriebssystemfunktion
FindAndMark() ist eine neue Funktion (erstmals in Version 6.2.4), die einen sehr schnellen Zugriff auf einzelne Tabellen erlaubt.
Hier die Syntax:
FindAndMark(Tabellenhande : INTEGER; Selektion : STRING) : INTEGER
Die Form einer Selektion (logischer Ausdruck) haben wir bereits im 5. Teil dieses Kurses besprochen. Die Arbeitsweise der Funktion ist recht schell erklärt:
Nur diejenigen Sätze der Tabelle, die die Selektion erfüllen, werden in die Markierungsliste aufgenommen. Das Funktionergebnis ist die Anzahl der gefundenen Datensätze, bzw.
ein negativer Fehlercode (beispielweise bei einer ungültigen Selektion).
Somit reicht ein einfacher Aufruf von
FindAndMark(db,'Ort= "München"')
um alle Münchener aus unserer Datenbank zu selektieren.
Hinweis: Die Funktion hat noch einen optionalen dritten Parameter Multiexpression (STRING), der für jeden gefundenen Datensatz berechnet wird. Das wird aber hier nicht
weiter behandelt.
Mit SortMark() können wir die Ergebnisliste nach Feldinhalten sortieren:
SortMark(Tabellenhandle : INTEGER; Indexbeschreibung : STRING) : INTEGER
Wieder taucht hier der Begriff Indexbeschreibung auf. Um die Sache nicht zu kompiliziert zu machen, geben Sie hier einfach die Namen der Felder ein (durch Komma getrennt),
nach denen sortiert werden soll.
SortMark(db,'Name,Vorname')
Die Funktion FirstMark() liefert die erste Satznummer aus der Markierungsliste, bzw. 0, wenn diese leer ist:
FirstMark(TabellenHandle : INTEGER) : INTEGER
NextMark() hat zwei Parameter:
NextMark(Tabellenhandle, Satznummer : INTEGER) : INTEGER
- 416 -
Betriebssystemfunktion
Als Satznummer muss hier diejenige übergeben werden, bzgl. der die nächste innerhalb der Markierungsliste gesucht wird. Somit erhalten wird ein typisches Programmfragment
für eine Datenbankabfrage mit Selektion und Sortierung der Ergebnisse:
VAR db, zeile : INTEGER
VAR Selektion, Sortierung : STRING
...
Selektion:='Ort="München"'
IFFindAndMark(db, Selektion)<=0 THEN
// Kein Treffer, eventuell TDB_LastError auswerten
ELSE
Sortierung:='Name,Vorname'
SortMark(db,Sortierung)
zeile:=FirstMark(db)
WHILE zeile>0 DO
ReadRec(db,zeile)
// Ausgabe der Felder ....
zeile:=NextMark(db,zeile)
END
END
43.2.11
Adressen-Suchmaschine
Zum Abschluss dieser Lektion wollen wir eine kleine Adressen-Suchmaschine »basteln«. Typischerweise bestehen solche Suchmaschinen aus nur einem
Formular, das im oberein Teil die Eingabe für die Suchbedingung enthält und das im unteren Teil die Treffer ausgibt. Wir vrwenden dazu folgendes Template:
<html>
<head>
<title>Adressen-Suchmaschine</title>
</head>
<body bgcolor="white">
<h2>Adressen-Suchmaschine</h2>
<form action="#action#" method="post">
<table border="1">
<tr>
<td>Suchbedingung: </td>
<td><input type="text" name="selection" value="#selection#"></td>
<td><input type="submit" name="submit" value="suchen"></td>
</tr>
</table>
<hr>
- 417 -
Betriebssystemfunktion
#treffer#
</form>
</body>
</html>
Speicher Sie dieses Template unter »tdbengine/templates/adressensuche.html« ab.
Unser Hauptprogramm kann recht einfach gehalten werden:
MODULE adressensuche.mod
PROCEDUREMain
CGIWriteLn('content-type: text/html')
CGIWriteLn('')
LoadTemplate('templates/adressensuche.html')
BearbeiteSuche
CGIWriteTemplate
ENDPROC
Die Prozedur »BearbeiteSuche« muss nun die übergebenen Werte aufbereiten. Zunächst werden die Platzhalter »#action#« und »#selection#« im Template ausgefüllt. Mit
»#action#« steht der Platzhalter für das CGI-Programm bereit, das die Formularauswertung vornimmt. Dieses ist freilich auch das Programm, das das Formular zum Klienten
schickt. Wir haben es also mit einem Selbstaufruf zu tun. Die URL zum eigenen Programm erhalten wir mit ParamStr(0).
Subst('#action#',ParamStr(0))
Etwas anders verhält es sich mit dem Platzhalter »#selection#«. Den Wert dafür erhalten wir aus dem Formular selbst mit CgiGetParam('selection').
Da wir den Inhalt aber auch für die Datenbankselektion selbst brauchen, wollen wir den Wert in einer Variablen speichern:
VAR user_selection : STRING
user_selection:=CGIGetParam('selection')
Subst('#selection#',user_selection,1) // ...,1, damit auch Umlaute stimmen
Schön, bleibt nur der Platzhalter »#treffer#«. Aber der hat es dafür auch in sich! Am einfachsten liegt der Fall, wenn wir nichts finden, denn dann reicht
SUBST('#treffer#','Leider nichts gefunden')
Andernfalls gehen wir so vor: Für jeden gefundenen Datensatz ersetzen wir »#treffer#« durch eine Ausgabe-Formatzeile, gefolgt vom Wort »#treffer#«. Wir ersetzen also den
Platzhalter durch sich selbst (und damit rekursiv). Nachdem auf diese Weise alle gefundenen Datensätze substituiert wurden, ersetzen wir »#treffer#« durch einen Leerstring.
- 418 -
Betriebssystemfunktion
Formatzeile:='#Vorname# #Name# #Strasse# #PLZ# #Ort#<br>'
Für jeden gefundenen Datensatz:
Subst('#treffer#',Formatzeile+'#treffer#')
Subst('#Vorname#',db,'Vorname',1)
Subst('#Name#',db,'Name',1)
... Subst('#treffer#','')
Der Gebrauch einer speziellen Formatzeile mag vielleicht etwas irritieren, sie bringt aber unglaubliche Flexibilität in unser Programm. Denn diese Zeile muss ja nicht unbedingt
im Programm selbst definiert werden, sie kann beispielsweise auch aus einer Konfigurationsdatei stammen... Wir wollen hier noch einmal kurz darstellen, was bei der
(rekursiven) Ersetzung von '#treffer#' passiert:
(ursprünglich im Template:) #treffer#
( nach SUBST('#treffer#',Formatzeile+'#treffer#'):) #Vorname# #Name# #Strasse# #PLZ# #Ort#<br>#treffer#
(nach SUBST('#Vorname#;db,'Vorname') etc.:) Hans Müller Amselweg 12 80335 München<br>#treffer#
... Mit diesem Vorüberlegungen sollte unser Suchmaschine leicht zu realisieren sein:
MODULE adressensuche.mod
PROCEDURE BearbeiteSuche
VAR user_selection : STRING
VAR db, zeile : INTEGER
VAR Formatstring : STRING
Subst('#action#',ParamStr(0))
user_selection:=CGIGetParam('selection')
Subst('#selection#',user_selection,1)
SetPara('ec 1')
IF db:=OpenDB('database/adressen.dat')=0 THEN
Subst('#treffer#','Datenbank NICHT gefunden')
ELSE
IFFindAndMark(db,user_selection)=0 THEN
Subst('#treffer#','Leider nichts gefunden')
ELSE
Formatstring:='#Name# #Vorname# #PLZ# #Ort#<br>'
- 419 -
Betriebssystemfunktion
SortMark(db,'Name,Vorname')
zeile:=FirstMark(db)
WHILE zeile DO
ReadRec(db,zeile)
Subst('#treffer#',Formatstring+'#treffer#')
Subst('#Name#',db,'Name',1)
Subst('#Vorname#',db,'Vorname',1)
Subst('#PLZ#',db,'PLZ',1)
Subst('#Ort#',db,'Ort',1)
zeile:=NextMark(db,zeile)
END
Subst('#treffer#','')
END
CloseDB(db)
END
ENDPROC
PROCEDUREMain
CGIWriteLn('content-type: text/html')
CGIWriteLn('')
LoadTemplate('templates/adressensuche.html')
BearbeiteSuche
CGIWriteTemplate
ENDPROC
Sie sollten dieses kleine Programm in allen Einzelheiten verstehen. Es bildet die Grundlage so vieler Internet-Anwendungen. In der nächsten Folge werden wir das Programm
weiterentwickeln und um eine Eingabemöglichkeit ergänzen.
Mit einer klitzekleinen Eweiterung erschließen wir unserer Suchmaschine noch wesentlich mehr Möglichkeiten: Ersetzen Sie hierzu die Zeile
user_selection:=CGIGetParam('selection')
durch
IFuser_selection:=GetQueryString('selection')=''
THENuser_selection:=CGIGetParam('selection')
END
- 420 -
Betriebssystemfunktion
Das heisst, dass wir zuerst den Query-String (also einen via URL übergebenen Wert) abfragen, bevor wir den Wert aus dem Eingabefeld des Formulars holen. Damit wird die
Suchmaschine auch über einen Link benutzbar. So können wie beispielsweise in ein HTML-Dokument folgendes einbauen:
<a href="/cgi-tdb/adressensuch.prg?selection=Ort='Berlin'">Alle Adressen aus Berlin</a>
Dadurch wird ein Hyperlink erzeugt, und wenn ein Anwender auf diesen klickt, wird unsere Suchmaschine gestartet, wobei die Selektion Ort='Berlin' vorab ausgewählt ist und
auch gleich bearbeitet wird. Genau nach dieser Methode arbeiten auch die großen Internet-Suchmaschinen, die neben dem eigentlichen Eingabefeld für Suchbegriffe meist eine
Reihe von Links (mit vorbelegten Suchen) anbieten.
- 421 -
Inhaltsverzeichnis
44.
JavaScript Lösungen
44.1
Zeitverzögerter Aufruf
<html>
<head>
<script>
var qryTimer =null;
function my_js_function(_p) {
console.log("div ",_p," aktiv", " => reaktion ")
}
function qryTimerOn(bOnChange,_o) {
var _id = _o.id
if (qryTimer)
clearTimeout(qryTimer);
if (bOnChange == true) {
qryTimer = setTimeout("my_js_function('"+_id+"')",1000);
}
}
</script>
</head>
<body>
<div onmouseover="qryTimerOn(true,this)"
color:yellow">11111</div>
<div onmouseover="qryTimerOn(true,this)"
<div onmouseover="qryTimerOn(true,this)"
<div onmouseover="qryTimerOn(true,this)"
color:black;color:white">4444</div>
onmouseout="clearTimeout(qryTimer)" id="f1" style="backgroundonmouseout="clearTimeout(qryTimer)" id="f2" style="background-color:blue">2222</div>
onmouseout="clearTimeout(qryTimer)" id="f3" style="background-color:silver">3333</div>
onmouseout="clearTimeout(qryTimer)" id="f4" style="background-
</html>
- 422 -
Inhaltsverzeichnis
45.
Linux Informationen
45.1
Dateien unter Linux zippen
z.B. alle tml Dateien in ../test.zip packen:
for i in *.tml ; do zip ../test.zip $i ; done
45.2
Alle Dateien in Kleinbuchstaben wandeln
ls | awk '{print "mv "$0" "tolower($0)}' | sh
- 423 -
Inhaltsverzeichnis
46.
Abbildungsverzeichnis
Abbildung 1:Beispielprogramm für gepuffertes Schreiben ............................................................................................................................................................ - 209 Abbildung 2:Beispielprogramm 2 für gepuffertes Schreiben ........................................................................................................................................................ - 209 -
- 424 -
Inhaltsverzeichnis
47.
Stichwortverzeichnis
[calls]
[time]
ABS()
access
andmarks
ARCCOS()
Base64
BaseDir
baseman
baseman.prg
Befehle
Tabelleöffnen
Mehrfach öffnen
setAlias
Byte[]
ChDir
ClearDat
Close
CloseDB
copyfile
CopyFile
CopyFile
CopyFile()
copymemo
CopyMemo
COS()
Crypt-Bibliothek
Datensatz kopieren
DBDir
DecodeB64
defile
DelDB
DelFile
DelFile()
- 305 - 305 - 131 - 110 - 110 - 132 - 300 - 209 - 27 - 87 -
DiskFree
DiskFree
DiskFree
DiskFree
dojo
autoComplete
ComboBox
data-dojo-type
FilteringSelectVarStore
getNextSelectionUrl
placeHolder
SimpleTextarea
tabindex
DOJO
doppelte Autonummern
DOS-Shell
EncodeB64
ENDSUB
Eot
EOT
Execprog
exit
EXP()
FindAuto
FirstDir
FirstRec
FRAC()
Free
FULLTEXT
Cut
DynKontraIndex
ExtABC
Fields
FulltextScanRecs
- 46 - 176 - 209 - 57 - 213 -, - 214 - 44 - 242 - 209 - 251 - 211 - 242 - 251 -, - 252 - 132 - 300 - 60 - 209 - 300 - 242 - 43 - 209 - 211 - 425 -
- 212 - 209 - 213 - 390 - 252 - 252 - 251 - 252 - 252 - 253 - 253 - 254 - 250 - 60 - 213 - 300 - 65 - 215 - 213 - 256 - 65 - 132 - 251 - 209 -, - 211 - 68 - 133 - 209 - 89 - 90 - 89 - 89 - 89 -
Inhaltsverzeichnis
Kontraindex
MaskenFeld
Modus
Step
GetDir
GetEnv
Getfield
getform.mod
getrec
GetRField
GetSize
Include
Initarray
INT()
IsFile
IsFile()
LabelNo
Laufzeitfehler
LOG()
LTrim
MakeDB
MakeDir
MakePW
MemoLen
NextDir
NextRec
nHandles
OpenDB
Performance
Praxistip
primfile
PrivDir
ramtext
~clip
AnsiToOem
cgiwritetemplate
copy
delete
GetIdent
- 90 - 90 - 89 - 89 - 209 - 209 -, - 213 - 173 - 254 - 176 - 251 - 256 - 213 - 177 - 133 - 209 - 211 - 182 - 173 - 134 - 251 - 88 - 209 -, - 210 - 300 - 251 - 209 -, - 212 - 68 - 209 - 251 - 305 - 245 - 52 - 209 -
getsize
InitSize
name
OemToAnsi
part
paste
SetIdent
subst
ramtext
find
ramtext
insert
ramtext
subst
Ramtext
RAMText_Delete
RAMText_Find
RAMText_Paste
RANDOM()
Read
ReadLn
readmemo
ReadRec
Relation
RemDir
rename
reset
Reset
Reset
rewrite
Rewrite
Rewrite
ROUND()
Satzbuffer
setauto
SetAuto
SIN()
SQRT()
STR()
- 246 - 242 - 241 - 245 - 245 - 242 - 426 -
- 249 - 244 - 246 - 242 - 246 - 245 - 242 - 241 - 244 - 244 - 246 - 241 - 251 - 251 - 252 - 134 - 213 -, - 214 - 213 -, - 215 - 242 - 251 - 66 - 209 -, - 210 - 242 - 242 - 213 -, - 214 - 252 - 242 - 214 - 251 - 135 - 176 - 57 - 57 - 135 - 131 - 136 -
Inhaltsverzeichnis
SUB
subst
sysbase
dojobutton.dat
dojoform.dat
dojoinput.dat
dojotyp.dat
tdb7labels.dat
Tabelle virtuell öffnen
TabelleLöschen
tappend
Tappend
TAppend
TESTBIT()
TestPW
- 65 - 247 -
trace_time
Übertragung von binären Dateien
Uses
U-Spezifikation
Volltext
GenRel
Volltext
GenList
Weite
Windows-Verzeichnis
Write
WriteLn
zuklären
cgilog_msg
- 251 - 251 - 251 - 251 - 251 - 43 - 43 - 242 - 214 - 214 - 137 - 300 -
- 427 -
- 305 - 300 - 214 - 32 - 88 - 88 - 216 - 213 - 214 -, - 215 - 214 -, - 215 - 357 -
Inhaltsverzeichnis
48.
Noch zu prüfenden Funktionen - 18.06.2012
AB
sprachliche Alternative für >= (veraltet)
ÄHNLICH
phonetischer Vergleich (veraltet)
ALLES
Symbol für kompletten Datensatz
AND
logisches UND
ansitooem()
Übersetzt eine String von ANSI nach ASCII
‘ANWEISUNGEN’
Die wesentlichen Programm-Bestandteile
asc() Ermittelt den ASCII-Code des ersten Zeichens der Zeichenkette
‘AUSDRÜCKE’
so sind arithmetische Ausdrücke aufgebaut
‘BEZEICHER’ so sind Bezeichner aufgebaut
BIS
sprachliche Alternative für <= (veraltet) oder TO
BYTE nimmt Ganzzahlen von 0 bis 225 auf
B_AND
binäres AND
B_NOT
binäres NOT (Komplement)
B_OR binäres OR
choice()
einen Wert aus einer Liste auswählen
chr() Zeichen aus einem ANSI-Code berechnen
close() Schließt eine Textdatei
closesock()
Schliesst eine bestehende TCP-Verbindung zu einem Server / Dienst
COMPLETE
Symbol für kompletten Datensatz
convline()
Konvertiert HTML-Text nach ASCII
count() Liefert die Anzahl von Datensätzen einer Auswertung
dat2dbf()
Konvertiert eine TDB-Tabelle in das DBF –Format
dbf2dat()
Konvertiert eine DBF-Tabelle (dBase, Foxpro) in das TDB-Format
decodeb64() Entschlüsselt einen String mit Base64
DEF
Definiert eine Inline-Funktion
‘DELSESSION’
digitstr()
natürliche Zahl als Ziffernfolge
DIV
Ganzzahlige Division ohne Rest
encodeb64() verschlüsselt einen String mit Base64
ENTHÄLT
Prüft, ob eine Zeichenkette in einer anderen enthalten ist (veraltet)
eot()
Zeigt das Ende einer Textdatei an
esgibt() Existenzoperator, veraltet für EXISTS
exchange()
Ersetzt in einem String Teilstrings
execmacro()
Führt eine Funktion in einem externen Modul aus
extnote()
Zwischenspeicher zum Informationsaustausch mit anderen EASY-Programmen
fillstr()
Füllt eine String auf
FROM
Anfang eines Bereiches festlegen
- 428 -
Inhaltsverzeichnis
genlist()
erzeugt eine Stichworttabelle
genrel() erzeugt die Relationstabelle für einen Volltextindex
getftinfo() befüllt ein Array mit der Angabe
getsession()
'GETSESSIONIDENT'
getsock()
Liest Daten aus einer bestehenden TCP-Verbindung
groß()
Übersetzt einen String in Großbuchstaben (veraltet)
GRÖßER
Vergleichs-Operator > (veraltet)
HAS
auf Teilstring prüfen
HAT
auf Teilstring prüfen (veraltet)
IF
Bedingte Anweisung
IN
Mengenoperator
'INDEXBESCHREIBUNG'
Indexdefinition und Sortierkriterium
isnumber()
Prüft auf Zahl
IST
Gleichheit (veraltet)
klein() übersetzt einen String in Kleinbuchstaben (veraltet)
KLEINER
steht für < (veraltet)
'KONSTANTEN'
Zeichenkonstanten und numerische Konstanten
länge() Anzahl der Zeichen in einem String (veraltet)
leftstr() linker Teilstring
length() Anzahl der Zeichen in einem String
LIKE Stringvergleich mit Wildcards
links()
linker Teilstring (veraltet)
loadtemplate() Lädt eine externe Textdatei in den System-Ramtext
lower() übersetzt einen String in Kleinbuchstaben
ltrim() entfernt linke Leerzeichen
makepw()
Verschlüsselt einen String im Unix-Standard
markbits()
Sucht im Volltextindex
marktable()
Sucht im Volltextindex
max()
Liefert den Maximalwert eines Ausdruck für eine Datenbankauswertung
md5_str()
Berechnet den MD5-Hashwert eines Strings
mean() Liefert das arithmetische Mittel eines Ausdruck für eine Datenbankauswertung
min() Liefert den Minimalwert eines Ausdruck für eine Datenbankauswertung
MOD Rest bei ganzzahliger Division
MODULE
Anfang eines Moduls
NICHT logisches NOT (veraltet)
nloop() mehrfache Wiederholung einer Ausdrucksberechnung
NOT Negation eines logischen Ausdrucks
note()
Zwischenspeicher für anderen Prozess
- 429 -
Inhaltsverzeichnis
note_a()
Zwischenspeicher-Array für anderen Prozess
note_b()
Zwischenspeicher-Array für anderen Prozess
note_i() Zwischenspeicher-Array für anderen Prozess
ntimes()
mehrfache Wiederholung eines Strings
ODER
Nichtausschließenden ODER (veraltet)
oemtoansi()
Übersetzt einen ASCII-String nach ANSI
opensock()
Stellt eine Verbindung zu einem beliebiegen Server her.
OR
Nichtausschließenden ODER
paramstr()
Argumente beim Programmaufruf
pos() Ermittelt das erste Vorkommen eines Teilstrings in einem String
primfile()
Setzt die Primärtabelle (veraltet)
PROCEDURE Leitet eine Prozedur ein
putsock()
Schreibt eine bestehende TCP-Stream-Verbindung
read() Liest einen String aus einer Textdatei
readln() Liest eine Zeile aus einer Textdatei
rechts() Liefert rechten Teilstring, veraltet
recno() Physikalische Satznummer des aktuellen Datensatzes
REPEAT
nicht-abweisende Programmschleife
reset()
Öffnet eine Textdatei zum lessen
RETURN
Prozedur beenden und Funktionsergebnis übermitteln
rewrite()
Öffnet einen Text zum Schreiben
rightstr()
Liefert den rechten Teil einer Zeichenkette
rtrim()
Entfernt rechte Leerzeichen
scan()
Zählt die Vorkommen eines Teilstrings
scanrec()
Fügt den aktuellen Datensatz in den Volltextindex ein
scanrecs()
Volltextindext neu erstellen
sel()
Konvertierung einer Selektion in eine Zahl
setident()
Setzt neuen Wert in Konfigurationsdatei
'SETSESSIONIDENT'
soundex()
liefert einen phonetischen String
stradd() Addition großer Zahlen als Strings
strcomp()
vergleicht zwei Strings
subpath()
String1 ohne String2
sum() Liefert die Summe eines Ausdruck für eine Datenbankauswertung
swap() Umkehrung eines Strings bzgl. des Sortierstarts
tappend()
Öffnet Textdatei zum Weiterschreiben
TBITS
Speichert eine Kette von einzelnen Bits
tdb_errorline() Zeilenummer im Programm
tdb_errormsg() Fehlerausdruck
- 430 -
Inhaltsverzeichnis
tdb_errorofs() Spalte innerhalb der von TDB_ErrorLine gemeldeten Zeile
tdb_errorpos() Zeichenposition im Programmtext
tdb_errorstr()
Liefert die Fehlermeldung zu einem Fehlercode
tdb_lasterror() Liefert den Fehlercode des letzen Fehlers
tdb_module()
Modul, in dem der Fehler auftrat
testln() String auf Inhalt prüfen und CR/LF anhängen
testpw()
Prüft einen String auf Übereinstimmung mit einem verschlüsselten Text
TO
Alias für <=
tohtml() Konvertiert eine Zeichenkette in den HTML-Zeichensatz
UND
logisches UND
upper() Zeichenkette in Großbuchstaben
USES
Einbindung eines externen Modules
valstr() Berechnet einen Ausdruck
VAR
Definiert eine oder eine Gruppe von Variablen
WHILE abweisende Programmschleife
WIE
Stringvergleich mit Wildcards (veraltet)
write()
Schreibt String in Textdatei
writeln()
Schreibt String (plus CR/LF) in Textdatei
xwert() Berechnet den n-ten Ausdruck in einer Liste (veraltet)
- 431 -

Documentos relacionados