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 -