Entwicklung eines Triggersubsystems auf dem Datenbank-Managementsystem Oracle7 Marcus Winkler Studienarbeit am Institut für Informatik der Universität Hannover Sept. 1994 Inhaltsverzeichnis 1 Einleitung 2 2 Integritätsüberwachung in Oracle7 2.1 Constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2 Trigger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 5 6 3 Erweiterung des Oracle7-Triggerkonzepts 3.1 Syntax verzögerter Trigger . . . . . . . . . 3.2 Ausführungssemantik . . . . . . . . . . . . 3.3 Umsetzung auf Oracle7 . . . . . . . . . . . 3.3.1 Die ∆-Tabellen und der Hilfstrigger 3.3.2 Speicherung verzögerter Trigger . . 3.3.3 Prozedur my commit . . . . . . . . 3.3.4 Einschränkungen . . . . . . . . . . 4 Programmbeschreibung 4.1 main.h . . . . . . . . . 4.2 main and oracle.pc . 4.3 head and body.c . . . 4.4 my commit.c . . . . . . 4.5 errors.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 14 18 23 24 28 29 32 . . . . . 33 35 36 38 39 40 5 Benutzerhandbuch 42 5.1 Triggererstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 5.2 Programmbedienung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 6 Erweiterungsmöglichkeiten 48 Literaturverzeichnis 49 1 Kapitel 1 Einleitung Die Überwachung von Integritätsbedingungen in aktiven relationalen Datenbanksystemen findet immer größeren Einsatz. Durch sie soll die logische Korrektheit von Datenbankinhalten gewährleistet werden. Dazu werden beim Datenbankentwurf Integritätsbedingungen aufgestellt, die, bezogen auf einzelne Relationen, die hinsichtlich der zu modellierenden Anwendung die technisch möglichen Datenbankzustände und -operationen (Einfügen / Löschen / Verändern) einschränken. Im Prinzip müßte ein Datenbanksystem alle Verletzungen von Integritätsbedingungen abfangen, sei es als Plausibilitätsprüfung gegen Eingabefehler oder als Kontrollinstrument für die Anwendungswelt. Die Überprüfung der Integritätsbedingungen zur Laufzeit kann durch einen universellen Integritätsmonitor vorgenommen werden. Dabei ist eine Überprüfung nach jeder Operation oder auch erst nach einer Folge von Operationen (Transaktion) denkbar. Als Reaktion des Monitors auf eine negativ ausgefallene Prüfung erfolgt das Rückgängigmachen der Operation bzw. Transaktion. Die von DBMSen heutzutage für die Integritätsüberwachung zur Verfügung gestellten Mittel sind Trigger. Trigger sind Prozeduren, die in einer Erweiterung der Datenbankanfragesprache (in der Regel SQL) geschrieben und in compilierter Form im Data Dictionary abgespeichert werden. Sie bestehen aus den folgenden drei Bausteinen: • Angabe der kritischen Datenbankoperation • Vorbedingung (allgemeines SQL-Prädikat) • Reaktionen (z.B. Fehlermeldung oder -korrektur) Die Ausführung der Trigger geschieht automatisch, so daß die Verantwortung dafür beim DBMS und nicht beim Benutzer liegt, und ist abhängig vom gewählten Triggertyp. Möglich ist die Ausführung • vor oder nach einer Änderung eines Datensatzes (Immediate Row / DatensatzTrigger), d.h. Tupelbezogen 2 KAPITEL 1. EINLEITUNG 3 • vor oder nach einer Operation (Immediate / Befehls-Trigger), d.h. nach einer Menge von Tupeln • nach einer aus mehreren Operatioen bestehenden Transaktion (Deferred / verzögerter Trigger) In den heute angebotenen kommerziellen DBMSen werden nur Befehls-, manchmal auch Datensatz-Trigger zur Verfügung gestellt. Es existiert zur Zeit kein kommerzielles relationales DBMS, das das Konzept der verzögerten Trigger implementiert hat. Unter den nichtkommerziellen Systemen ist Starburst bekannt [Widom, 1992], [Widom, 1993]. Im Bereich der objekt-orientierten Datenbanken werden verzögerte Trigger bereits unterstützt. Ein Beispiel dafür ist Ode [Gehani and Jagadish, 1991]. Dabei haben diese Trigger eine große Bedeutung in der Anwendung. Bei referentiellen Abhängigkeiten zwischen Relationen kommt man ohne sie nur mit großen Einschränkungen in der Integritätsüberwachung oder im Anwendungskomfort aus. Bei der Implementation verzögerter Trigger stellen sich einige Fragen: • Was passiert, wenn mehrere Trigger in einem DB-Zustand gleichzeitig gefeuert werden müßten, weil deren Vorbedingungen gleichzeitig erfüllt werden ? • Werden alle ausgeführt, und wenn ja, in welcher Reihenfolge und auf welchen Daten jeweils ? • Kann die Ausführung eines Triggers weitere auslösen, und kann dabei ein Zyklus erkannt werden bzw. sind Zyklen bereits bei der Spezifikation der Trigger erkennbar ? • Wenn in einem DB-Zustand mehrere Trigger gefeuert werden müßten und noch andere Trigger auf ihre Feuerung warten, wie ist da die Reihenfolge der Abarbeitung geregelt ? • Kann der zuerst ausgeführte Trigger die Vorbedingungen anderer Trigger, deren Feuerung zurückgestellt wurde, wieder ungültig machen ? Gegenstand der Studienarbeit ist es nun, auf das Oracle7 DBMS ein Triggersubsystem aufzusetzen, das die Verwendung verzögerter Trigger ermöglicht. Dabei sollen mehrere Trigger unabhängig voneinander auf einer Tabelle arbeiten können und die Abarbeitungsreihenfolge durch Prioritäten bestimmt sein. Die während der Transaktion neu eingefügten und die gelöschten Tupel sollen ebenso referenzierbar sein wie die veränderten Tupel vor und nach einem Update. Das Transaktionsende soll als Auslöser der Triggerabarbeitung vom Anwender durch den Befehl my commit gekennzeichnet werden. KAPITEL 1. EINLEITUNG 4 Oracle7 besitzt eine prozedurale Erweiterung von SQL namens PL/SQL, mit der es möglich ist, Prozeduren und Trigger zu erstellen (vgl. [O7-PL/SQL]). Prozeduren sind PL/SQL-Blöcke, die, wie in Programmiersprachen üblich, mit ihrem Namen aufgerufen werden. Mit Hilfe von Datensatz-Triggern und Prozeduren können nun verzögerte Trigger realisiert werden. Dabei wird allerdings keine so hohe Performance erreicht wie bei einem eingebauten Triggersystem mit entsprechender Funktionalität. Oracle7 ist jedoch nicht auf einer dem DBMS näheren Ebene zu programmieren. Es bleibt also nur die Möglichkeit des Aufsatzes. Weitere Arbeiten, die sich mit verzögerten Triggern [Gertz and Lipeck, 1993] sowie [Ceri and Widom, 1990]. beschäftigen, sind In den folgenden Kapiteln werden das Triggerkonzept von Oracle7 sowie die Umsetzung verzögerter Trigger mit den Möglichkeiten dieses DBMS beschrieben. Es folgen die Beschreibung des Programmablaufs und der Modularisierung. Das Benutzerhandbuch erklärt die Vorgehensweise beim Erstellen der Trigger. Den Abschluß bildet ein Ausblick auf Erweiterungsmöglichkeiten des Programms. Kapitel 2 Integritätsüberwachung in Oracle7 In Oracle7 gibt es prinzipiell zwei Möglichkeiten, Integritätsbedingungen zu spezifizieren: mit Constraints oder mit Triggern. 2.1 Constraints Constraints werden bei der Tabellendefinition (oder -änderung) angegeben. So kann man z.B. durch Angabe von not null erreichen, daß in einer Spalte keine Nullwerte auftreten dürfen. Mit der Angabe unique wird es unmöglich, daß in einer Spalte ein Wert mehrfach auftritt. Mit primary key und references bzw. foreign key references werden Primär- und Fremdschlüsselbedingungen ausgedrückt. Folgt auf eine Primärschlüsselbedingung noch die on delete cascade-Klausel, so werden beim Löschen des Primärschlüssels auch gleich alle zugehörigen Fremdschlüssel gelöscht. Mit Hilfe der check-Bedingung können logische Ausdrücke formuliert werden, die überprüft werden sollen. Jede dieser Überprüfungen findet unmittelbar nach der auslösenden DB-Operation statt. Ein Beispiel für die Verwendung solcher Constraints ist die folgende Definition einer Angestelltenrelation, die die Nummer, den Namen, und das Gehalt eines Angestellten verwaltet: create table EMP values(empno number(5) primary key, ename varchar2(15) not null, sal number(7,2), check (sal > 0)); Dabei ist die Angestelltennummer Primärschlüssel der Tabelle. Der Namenseintrag darf nie fehlen, und das Gehalt muß immer positiv sein. In der folgenden Abteilungsrelation soll die Angestelltennummer referenziert werden. Sie besteht aus den Angaben von Nummer, Name und Abteilungsleiter. Der Abteilungsleiter muß in der Angestelltentabelle eingetragen sein. 5 KAPITEL 2. INTEGRITÄTSÜBERWACHUNG IN ORACLE7 6 create table DEPT values(deptno number(4), dname varchar2(15) not null, mgr number(5) not null references EMP on delete cascade); Einfache Integritätsbedingungen und vor allem referentielle Abhängigkeiten können also mit Constraints ausgedrückt werden. Sollen kompliziertere Abhängigkeiten geprüft oder DB-Operationen als Reaktion auf ein auslösendes Ereignis durchgeführt werden, die über ein einfaches Löschen (on delete cascade) oder ein Zurücksetzen in den DB-Zustand vor dem auslösenden Ereignis hinausgehen, so werden Trigger notwendig. 2.2 Trigger Ein Trigger wird in Oracle7 immer auf genau einer Tabelle definiert. Auf einer Tabelle wiederum können maximal 12 verschiedene Trigger definiert werden. Dabei unterscheidet man das Auslöseereignis, also die auslösende Datenbankoperation, den Zeitpunkt der Feuerung (before / after) und den Triggertyp (Befehls-/ Datensatz-Typ). Die folgende Tabelle stellt die verschiedenen Definitionsmöglichkeiten dar: Zeitpunkt Ereignis Triggertyp before after Befehl Datensatz insert ? ? ? ? delete ? ? ? ? update ? ? ? ? Dabei kann man auch verschiedene Auslöseereignisse kombinieren, z.B. kann man einen after insert or update-Trigger konstruieren, was jedoch die Anzahl der Trigger einschränkt. Ein weiterer after update-Trigger wäre hier nicht mehr möglich. Will man mehrere Integritätsbedingungen umsetzen, die nach demselben Ereignis überprüft werden sollen, so müssen alle im selben Trigger untergebracht werden. Dabei ist die Abarbeitungsreihenfolge zu beachten. Ein Befehls-Trigger wird immer genau einmal ausgeführt, unabhängig davon, wieviele Datensätze durch die auslösende Operation verändert wurden. Ein Datensatz-Trigger hingegen wird entsprechend der Anzahl der veränderten Datensätze gefeuert. D.h. werden drei Datensätze verändert, wird der Datensatz-Trigger dreimal ausgeführt, der Befehls-Trigger KAPITEL 2. INTEGRITÄTSÜBERWACHUNG IN ORACLE7 7 jedoch nur einmal. Die verschiedenen Trigger werden in der folgenden Reihenfolge abgearbeitet: 1. Before-Befehls-Typ: einmal vor der Ausführung der auslösenden Operation 2. Before-Datensatz-Typ: n-mal vorher, evtl. gar nicht bei n=0 3. After-Datensatz-Typ: n-mal nachher 4. After-Befehls-Typ: einmal nachher Bei den Datensatztriggern steht n für die Anzahl der Tupel, die von der auslösenden DBOperation verändert bzw. eingefügt oder gelöscht wurden. Bei Datensatz-Triggern ist es möglich, auf die alten und neuen Tabelleneinträge zuzugreifen. Die Präfixe :new. bzw. :old. vor dem entsprechenden Spaltennamen erlauben den Zugriff. Die folgende Tabelle zeigt, welche Werte abhängig von der DB-Operation jeweils referenziert werden können: Ereignis Adressierung der Datensatz-Instanzen insert old new null neue Werte update old new alte Werte neue Werte delete old new alte Werte null Soll beispielsweise aus einer Angestelltenrelation EMP die Anzahl der Angestellten bestimmt werden, die eine Gehaltserhöhung erhalten haben, so sähe dies wie folgt aus: select count(*) into anzahl from EMP where :new.sal > :old.sal; Zur Definition eines Triggers dient der create trigger-Befehl. Definiert werden dabei: • der Triggername • der Triggerzeitpunkt (before / after) • die auslösenden DB-Operationen (insert / delete / update) KAPITEL 2. INTEGRITÄTSÜBERWACHUNG IN ORACLE7 8 • der Tabellenname • der Triggertyp (Befehls-/ Datensatz-Typ) • die Triggerrestriktion • und der Triggerrumpf, in dem sich die eigentliche Prozedur befindet. Bei einem Update-Trigger kann zusätzlich angegeben werden, welche Spalten eine UpdateOperation verändern muß, um die Feuerung des Triggers auszulösen. Innerhalb der Triggerrestriktion kann man eine zusätzliche Bedingung angeben, die vor der Triggerausführung überprüft werden muß. Nur wenn die Bedingung zutrifft, wird der Triggerrumpf ausgeführt, sonst wird die Trigger-Ausführung schon an dieser Stelle positiv beendet. In PL/SQL-Syntax sieht der Triggeraufbau wie folgt aus (vgl. [O7-AppDev]): create or replace trigger <trig name> Triggername before / after Triggerzeitpunkt insert or update of <Spalte1>, <Spalte2>, . . . or delete DB-Operation on <table name> Tabellenname (for each row) Triggertyp when <Bedingung> Triggerrestriktion declare <Variablen- und Cursor-Deklarationen> Triggerrumpf <PL/SQL-Programm-Code> Innerhalb des Triggerrumpfs steht ein PL/SQL-Block (siehe dazu [O7-PL/SQL]), der das eigentliche Triggerprogramm enthält. Ist ein Datensatz-Trigger für mehrere DBOperationen definiert, so kann bei diesem mit den Konstrukten KAPITEL 2. INTEGRITÄTSÜBERWACHUNG IN ORACLE7 if if if if 9 inserting then <SQL-Block> updating then <SQL-Block> updating (’<Spalte>’) then <SQL-Block> deleting then <SQL-Block> eine Programmverzweigung in Abhängigkeit von den entsprechenden Ereignissen bewirkt werden. Zum PL/SQL-Befehlssatz gehören auch die insert-, delete- und update-Operationen. Da auf diese wieder Trigger gefeuert werden können (Kaskade), muß man bei den Triggerdefinitionen darauf achten, daß kein Zyklus entsteht, also z.B. keine Triggerausführung immer wieder die eigene Feuerung nach sich zieht. Das Oracle7 DBMS ist nicht in der Lage, zyklische Trigger zu erkennen, da der Zyklus im PL/SQL-Block codiert ist. Mittels des Prozeduraufrufs raise application error kann innerhalb des PL/SQLBlocks eine Oracle7-Fehlermeldung ausgegeben werden; dabei wird außerdem der Trigger negativ beendet. Was bedeutet es nun eigentlich, wenn ein Trigger positiv oder negativ beendet wird ? Da erst die gefeuerten Trigger eine DB-Operation abschließen, führt ein negatives Triggerende dazu, daß alle DB-Operationen, die während der Abarbeitung des letzten Triggers durchgeführt wurden, rückgängig gemacht werden. Eine positive Triggerausführung läßt die geänderten DB-Inhalte bestehen. Mit dem Kommando alter trigger trig name ist es möglich, Trigger vorübergehend auszuschalten, so daß während dieser Zeit die dem Trigger zugrunde liegenden Integritätsbedingungen verletzt werden können. Das vollständige Löschen eines Triggers geschieht mit dem Befehl drop trigger trig name. Das Oracle7 DBMS speichert Trigger zunächst nicht in compilierter Form, sondern übersetzt den PL/SQL-Code erst bei der ersten Feuerung. Danach liegt der Trigger dann in compilierter Form vor, so daß die folgenden Triggerausführungen erheblich schneller werden. Eine wichtige Eigenschaft von Triggern ist es auch, daß im Triggerrumpf keinerlei Einschränkungen gegenüber den PL/SQL-Prozeduren gemacht werden. Es muß also nicht unbedingt eine die logische Korrektheit erhaltende Reaktion auf das auslösende Ereignis im Rumpf stehen wie etwa die Ausgabe einer Fehlermeldung oder ein Rollback. Genausogut können dort Algorithmen untergebracht werden, die gewisse Teile der Relation nach einer bestimmten DB-Operation kopieren sollen. Auf der anderen Seite kann man nicht jede Integritätsbedingung in Constraints oder in Trigger übersetzen. Um Beispiele dafür geben zu können, wird die folgende Ange- KAPITEL 2. INTEGRITÄTSÜBERWACHUNG IN ORACLE7 10 stellten-Relation benutzt, die die Grundlage aller weiteren Beispiele sein wird. Zugleich ist sie Teil der Oracle7 Demonstrationsrelation. Sie besteht aus den Tabellen EMP und DEPT. EMP enthält die Daten der Angestellten: Nummer, Name, Beruf, Vorgesetzter, Einstellungsdatum, Gehalt und Abteilungsnummer. DEPT beinhaltet Angaben über die Abteilungen: Nummer, Name, Ort und Abteilungsleiter. EMP DEPT empno ename job mgr hiredate deptno dname loc sal deptno mgr Es gelte die folgende Integritätsbedingung: Abteilungsleiter beziehen das höchste Gehalt in ihrer Abteilung. Hier wäre ein Vergleich des Gehalts des Abteilungsleiters mit den Gehältern seiner Mitarbeiter nötig. Da man bei Constraints keine select-Operationen verwenden kann, müßte man Trigger einsetzen. Doch ein Immediate-Trigger kann auf einer sich ändernden Tabelle keine Aggregation durchführen. In diesem Falle wäre dies das Bestimmen des maximalen Gehalts der Mitarbeiter eines Abteilungsleiters. Der Einsatz eines verzögerten Triggers wird notwendig. Nun sollen die folgenden zwei Integritätsbedingungen umgesetzt werden: 1. Zu jeder Abteilung existiert ein Abteilungsleiter. 2. Jeder Angestellte arbeitet in genau einer Abteilung. Zunächst folgt die Umsetzung mit Constraints: create table EMP (empno number(4) primary key, ename varchar2(10), job varchar2(9), mgr number(4), hiredate date, sal number(7,2), deptno number(2) not null references dept(deptno)) KAPITEL 2. INTEGRITÄTSÜBERWACHUNG IN ORACLE7 11 create table DEPT (deptno number(2) primary key, dname varchar2(14), loc varchar2(13), mgr number(4) not null references emp(empno)) Für die Überwachung dieser Integritätsbedingungen mit Triggern muß man zwei Trigger erstellen: einen für die Tabelle DEPT und einen für die Tabelle EMP. Beide sind erheblich aufwendiger zu erzeugen, und ihre Abarbeitung dauert durch die select-Operationen auf den anderen Tabellen länger als die obigen Constraints. Dafür werden noch Fehlermeldungen ausgegeben, bevor das Rollback durchgeführt wird. create trigger dept_trig after insert or update or delete on DEPT for each row declare anzahl number; begin if deleting /∗ Anzahl der Angestellten in der gelöschten Abteilung > 0 ? ∗/ then select count(empno) into anzahl from EMP where deptno=:old.deptno; if anzahl>0 then raise_application_error(-20001,’In der Abteilung ’ || :new.deptno || ’ arbeiten noch Angestellte !’); end if; /* Anzahl der Angestellten, die Abteilungsleiter sind, = 0 ? */ else select count(empno) into anzahl from EMP where empno=:new.mgr; if :new.mgr is null /* kein Abteilungsleiter */ or anzahl=0 then raise_application_error(-20002,’Zur Abteilung ’ || :new.deptno || ’ existiert kein Abteilungsleiter ’ || ’in der Tabelle EMP !’); end if; end if; end; KAPITEL 2. INTEGRITÄTSÜBERWACHUNG IN ORACLE7 12 create trigger emp_trig after insert or update or delete for each row declare anzahl number; begin if deleting /* Anzahl der vom Angestellten geleiteten Abteilungen > 0 ? */ then select count(deptno) into anzahl from DEPT where mgr=:old.empno; if anzahl>0 then raise_application_error(-20003,’Der Angestellte ’ || :old.empno || ’ ist noch Abteilungsleiter einer Abteilung !’); end if; /* Anzahl der Abteilungen, in der der Angestellte arbeitet, = 0 ? */ else select count(deptno) into anzahl where deptno=:new.deptno; if :new.deptno is null or anzahl=0 then raise_application_error(-20004,’Der Angestellte ’ || :new.empno || ’ arbeitet in keiner existierenden Abteilung’); end if; end if; end; Will man in die anfangs noch leere Relation einen Angestellten einfügen, so muß man zuerst eine Abteilung einfügen. Eine Abteilung kann jedoch erst eingegeben werden, wenn schon ein Abteilungsleiter in der Angestellten-Tabelle existiert. Einer der Constraints bzw. Trigger für die erste oder die zweite Integritätsbedingung muß also erst einmal abgeschaltet und später wieder aktiviert werden. Es zeigt sich, daß der Eingabekomfort stark eingeschränkt wird. Außerdem hat es wenig Sinn, Integritätsbedingungen aufzustellen, die die Eingabe überwachen sollen, dann aber genau bei dieser Aktion außer Kraft gesetzt werden. KAPITEL 2. INTEGRITÄTSÜBERWACHUNG IN ORACLE7 13 Hier würden nun verzögerte Trigger Abhilfe schaffen. Man könnte die Daten in beliebiger Reihenfolge in die Relation einfügen, und am Ende würde die logische Korrektheit der Transaktion geprüft werden. Kapitel 3 Erweiterung des Oracle7-Triggerkonzepts 3.1 Syntax verzögerter Trigger Ein verzögerter Trigger besteht wie ein Immediate-Trigger auch aus der Angabe des bzw. der auslösenden Ereignisse, der Vorbedingung und der Reaktion. Im Triggerkopf sind anzugeben: • der Triggername • die auslösenden Ereignisse • der Tabellenname Im Triggerrumpf wiederum steht das eigentliche Triggerprogramm, in dem die Vorbedingung geprüft und entsprechend reagiert wird. Im Rumpf ist es außerdem möglich, die neu eingefügten, die gelöschten und die veränderten Tupel vor und nach der Änderung zu referenzieren. Die Syntax der verzögerten Trigger ist stark an die der in Oracle7 vorhandenen Trigger angelehnt. Neu ist das zusätzliche Schlüsselwort deferred. Im Triggerkopf müssen der Triggername, die auslösenden Ereignisse und der betreffende Tabellenname spezifiziert werden: 14 KAPITEL 3. ERWEITERUNG DES ORACLE7-TRIGGERKONZEPTS 15 create deferred trigger <trig name> Triggername after insert or update or delete DB-Operation on <table name> Tabellenname (declare) optional <Variablen- und Cursor-Deklarationen> Triggerrumpf <PL/SQL-Programm-Code> Dabei kann die declare-Anweisung verwendet werden, wie es bei den Immediate-Triggern verlangt wird, sie kann aber auch weggelassen werden, wie es bei den Prozeduren in Oracle7 gemacht werden muß. Nur um das look-and-feel der Immediate-Trigger bei den verzögerten Triggern zu erhalten, wurde diese Angabe zugelassen. Innerhalb des Triggerrumpfs können auch bei den verzögerten Triggern Verzweigungen in Abhängigkeit vom auslösenden Ereignis durchgeführt werden, wie es bei den DatensatzTriggern auch der Fall ist. Die zur Verfügung stehenden Konstrukte sind: if inserting then <SQL-Block> if updating then <SQL-Block> if deleting then <SQL-Block> Dabei ist zu beachten, daß beim Updating-Konstrukt keine Spaltenangabe mehr erlaubt ist. Dasselbe gilt für den Triggerkopf, wo als auslösendes Ereignis auch nur das allgemeine Update möglich ist. Dadurch wurde die Implementation vereinfacht. Bei den ImmediateTriggern war an dieser Stelle noch die Einschränkung auf bestimmte Spalten erlaubt. Aus demselben Grunde fehlt bei den verzögerten Triggern im Vergleich zu den vorhandenen die Restriktion im Triggerkopf. Die Triggerbedingung muß im Triggerrumpf spezifiziert werden. Über die Priorität eines Triggers werden explizit keine Angaben gemacht. Da die verzögerten Trigger alle in eine Datei geschrieben werden müssen, bevor sie in Prozeduren bzw. Trigger für die Differenztabellen umgewandelt werden, wird ihre Reihenfolge in der Datei KAPITEL 3. ERWEITERUNG DES ORACLE7-TRIGGERKONZEPTS 16 als implizite Angabe von Prioritäten angesehen. D.h. der Trigger mit der höchsten Priorität steht in der Datei an erster Stelle. Will man nun die Integritätsbedingung, die verlangt, daß in einer Abteilung der Abteilungsleiter das höchste Gehalt bezieht, mit einem verzögerten Trigger umsetzen, so sieht dieser wie folgt aus: create deferred trigger gehalt after insert or update on emp declare /* Cursor lesen zeilenweise Tupel aus einer Tabelle aus. Dazu werden sie mit open initialisiert und in einer Schleife mit fetch ausgelesen. Mit close wird der Vorgang abgeschlossen. */ /* liest die neu eingetragenen Manager aus, deren Gehalt niedriger als das eines Mitarbeiters ist */ cursor ins_mgr_cur is select empno from i_gehalt i where exists (select empno from emp where mgr = i.empno and sal > i.sal); /* diejenigen neuen Angestellten, deren Manager weniger verdient als sie */ cursor ins_emp_cur is select empno from i_gehalt i where exists (select empno from emp where i.mgr = empno and sal < i.sal); /* Manager und ihr altes Gehalt, wo das neue geringer ist als das eines Mitarbeiters */ cursor upd_mgr_cur is select new_empno, old_sal from u_gehalt u where exists (select empno from emp where mgr = u.new_empno and sal > u.new_sal); /* Angestellte und ihr altes Gehalt, deren Manager weniger verdient */ cursor upd_emp_cur is select new_empno, old_sal from u_gehalt u KAPITEL 3. ERWEITERUNG DES ORACLE7-TRIGGERKONZEPTS where exists (select empno from emp where u.new_mgr = empno and sal < u.new_sal); ins_mgr number(4); ins_emp number(4); upd_mgr upd_mgr_cur%rowtype; upd_emp upd_emp_cur%rowtype; begin if inserting then /* alle neuen Manager entfernen, die zu wenig verdienen */ open ins_mgr_cur; loop fetch ins_mgr_cur into ins_mgr; exit when ins_mgr_cur%notfound; delete from emp where empno = ins_mgr; end loop; close ins_mgr_cur; /* alle neuen Angestellten entfernen, die zuviel verdienen */ open ins_emp_cur; loop fetch ins_emp_cur into ins_emp; exit when ins_emp_cur%notfound; delete from emp where empno = ins_emp; end loop; close ins_emp_cur; end if; if updating then /* Gehalt der Manager mit zu niedrigem neuen Gehalt auf alten Wert setzen */ open upd_mgr_cur; loop fetch upd_mgr_cur into upd_mgr; exit when upd_mgr_cur%notfound; update emp set sal = upd_mgr.old_sal where empno = upd_mgr.new_empno; end loop; close upd_mgr_cur; 17 KAPITEL 3. ERWEITERUNG DES ORACLE7-TRIGGERKONZEPTS 18 /* Gehalt der Angestellten mit zu hohem neuen Gehalt auf alten Wert setzen */ open upd_emp_cur; loop fetch upd_emp_cur into upd_emp; exit when upd_emp_cur%notfound; update emp set sal = upd_emp.old_sal where empno = upd_emp.new_empno; end loop; close upd_emp_cur; end if; end; Es werden als Reaktion auf Einträge, die die Integritätsbedingung verletzen, die Gehälter der Betroffenen wieder auf ihren alten Wert gesetzt. Zunächst werden die neu eingefügten Tupel untersucht, danach die veränderten. Dabei werden erst die Abteilungsleiter und dann die Mitarbeiter geprüft und gegebenenfalls deren Gehälter geändert. 3.2 Ausführungssemantik Um den notwendigen Abarbeitungsmechanismus verzögerter Trigger zu beschreiben, wird eine mengenorientierte Beschreibung des Transaktionsbegriffs eingeführt (vgl. [Hanson and Widom, 1992]). Zunächst sei die folgende Syntax für einen Block von SQL-Operationen gegeben: transaction := sql − op; sql − op; . . . ; sql − op sql − op := update − op | delete − op | insert − op update − op := update table set columns = expressions where predicate delete − op := delete from table where predicate KAPITEL 3. ERWEITERUNG DES ORACLE7-TRIGGERKONZEPTS 19 insert − op := insert into table values hv1 , v2 , . . . , vn i | insert into table { select − op } select − op := select columns from tables where predicate Die Prädikate können dabei beliebig komplex sein und auch select-Operationen enthalten. Innerhalb einer Tabelle wird eine Zeile, also ein Tupel, eindeutig durch ihren TupleIdentifier identifiziert. Auch wenn man alle Werte eines Tupels ändert, der Tupelidentifier bleibt erhalten. Betrachtet man die Veränderung eines DB-Zustands durch einen Block von DB-Operationen, so kann man den Nettoeffekt beschreiben als • die neu eingefügten Tupel - das sind die Tupel, die nach dem Block existieren, jedoch vorher nicht vorhanden waren. + die gelöschten Tupel - das sind die Tupel, die vor dem Block vorhanden waren, aber nicht mehr danach. + die veränderten Tupel - das sind die Tupel, die nach dem Block andere Werte haben als vorher. Man kann also jedem Block von DB-Operationen ein Tripel (I,D,U) zuordnen, wobei I die Menge der neu eingefügten Tuple-Identifier ist, D die Menge der nicht mehr vorhandenen und U die Menge derjenigen, wo die Tupel veränderte Werte enthalten. Die Ausführung eines Blocks von DB-Operationen nennt man Transition, und mit dem Effekt einer Transition bezeichnet man das zugehörige Tripel (I,D,U). Dabei sind einige Regeln zu beachten. Ein Tupel, das während eines Blocks neu eingefügt und später dann verändert wird, wird genau so behandelt, als ob das Tupel nur mit den zuletzt gültigen Werten eingefügt worden wäre. Ein Tupel, das erst eingefügt und dann wieder gelöscht wird, tritt im Nettoeffekt gar nicht auf. Ein Tupel, das mehrfach verändert und danach gelöscht wurde, wird so behandelt, als ob das Tupel mit den ursprünglichen Werten gelöscht wurde. Wurde es lediglich mehrfach verändert, dann betrachtet man nur den Original- und den Endzustand des Tupels. Ein Tupel, das zunächst gelöscht, später aber wieder neu eingefügt worden ist, hat einen neuen Tuple-Identifier erhalten, gilt also KAPITEL 3. ERWEITERUNG DES ORACLE7-TRIGGERKONZEPTS 20 als gelöscht und neu eingefügt. Findet nun eine Transition T1 statt, so ändert sich der DB-Zustand S0 nach S1 . Dabei tritt der Effekt E1 auf: E1 S1 S0 T1 Die nachfolgende Tabelle gibt an, welcher Effekt E1 entsteht, wenn die Transaktion T1 aus zwei DB-Operationen besteht, die jeweils ein Tupel t einfügen I(t), verändern U(t,t0 ) oder löschen D(t): T1 I(t) U(t,t0 ) I(t) D(t) U(t,t0 ) U(t0 ,t00 ) U(t,t0 ) D(t0 ) U(t,t0 ) U(t0 ,t) I(t) D(t) E1 0 I(t ) ∅ U(t,t00 ) D(t) ∅ D(t) ∪ I(t) Jeder verzögerte Trigger soll die Einhaltung der ihm zugrundeliegenden Integritätsbedingungen am Ende einer Transaktion gewährleisten. Also wird jedem Trigger ein Effekt E zugeordnet, der den Nettoeffekt der DB-Operationen seit der letzten Feuerung des Triggers bzw. dessen Definition beschreibt. Wurden mehrere Trigger definiert, so unterscheiden sich die für sie relevanten Effekte in Abhängigkeit von der Feuerungsreihenfolge. Es befinde sich eine Datenbank zunächst im Zustand S1 nach einer Transaktion T1 mit Effekt E1 . Dieser Effekt sei zunächst für alle verzögerten Trigger relevant. Nun werde ein Trigger Ri aufgrund des Effekts E1 gefeuert. Dann finde durch die Triggerausführung eine weitere Transition TRi mit Effekt E2 statt. Ein neuer DB-Zustand S2 wird erreicht. Zu allen Triggern Rj 6= Ri gehört jetzt der Effekt E20 := E1 ◦ E2 . Lediglich zum Trigger Ri gehört nur der Effekt E2 , da bei diesem auf den Effekt E1 schon reagiert wurde. Siehe dazu auch die folgende Tabelle: E1 I(t) I(t) U(t,t0 ) U(t,t0 ) U(t,t0 ) D(t) E2 U(t,t0 ) D(t) U(t0 ,t00 ) D(t0 ) U(t0 ,t) I(t) E1 ◦ E2 I(t0 ) ∅ U(t,t00 ) D(t) ∅ D(t) ∪ I(t) KAPITEL 3. ERWEITERUNG DES ORACLE7-TRIGGERKONZEPTS 21 Es wird deutlich, daß sich mit jeder weiteren Transition TRj , die durch die Abarbeitung eines Triggers entsteht, die für die Trigger relevanten Effekte immer mehr voneinander unterscheiden (in einem DB-Zustand Sl ). Während einer Triggerabarbeitung verändert sich nicht der zugrundeliegende Effekt, denn erst eine Transition – in diesem Falle also die vollständige Abarbeitung des Triggers – führt in diesem Modell zu einem neuen DB-Zustand mit dazugehörigem neuen Effekt. Erst wenn für jeden Trigger gilt, daß seine Bedingung auf dem zu ihm gehörenden Effekt nicht mehr erfüllt wird, wenn also ein Endzustand erreicht ist, spricht man vom Transaktionsende. Wird innerhalb einer Triggerabarbeitung ein Rollback durchgeführt, so werden alle DBOperationen bis zum letzten Commit zurückgenommen. Falls nicht durch einen verzögerten Trigger ein Commit durchgeführt wurde, sind die zu allen verzögerten Triggern gehörenden Effekte leer. Wurde jedoch ein Commit durch einen verzögerten Trigger ausgeführt und daraufhin der Zustand Si erreicht, so entsprechen die zu den verzögerten Triggern gehörenden Effekte nach dem Rollback denen, die sie im Zustand Si besaßen. Wird von einem verzögerten Trigger eine Fehlermeldung mit Hilfe der Funktion raise application error ausgegeben, so werden alle von diesem Trigger durchgeführten DB-Operationen zurückgenommen und die Feuerung aller Trigger abgebrochen. Für jeden Trigger ist dann wieder derselbe Effekt relevant, wie vor der Feuerung des letzten Triggers. Um ein Beispiel für den Abarbeitungsmechanismus zu geben, wird folgende Integritätsbedingung auf der Angestellten-Relation EMP aufgestellt, die einen kaskadierenden Trigger zur Folge hat, der also aufgrund seines Effekts wieder gefeuert werden kann: Wird ein Abteilungsleiter versetzt, so müssen auch alle seine Mitarbeiter in die neue Abteilung versetzt werden. Der dazugehörige prüfende verzögerte Trigger hat dann folgendes Aussehen: create deferred trigger Manager_Versetzung after update on emp declare /* liest die Daten der versetzten Vorgesetzen mit Mitarbeitern*/ cursor man_cur is select * from u_manager_versetzung u where old_deptno != new_deptno and exists (select empno from emp where u.new_empno = mgr); KAPITEL 3. ERWEITERUNG DES ORACLE7-TRIGGERKONZEPTS 22 man u_manager_versetzung%rowtype; begin open man_cur; loop fetch man_cur into man; exit when man_cur%notfound; /* versetze alle Mitarbeiter der Vorgesetzen in dessen neue Abteilung. */ update emp set deptno = man.new_deptno where mgr = man.old_empno; end loop; close man_cur; end; Die Ausprägung der Relation EMP vor der Transaktion im Zustand S0 sei die folgende: EMPNO DEPTNO MGR JOB ENAME ---------- ---------- ---------- --------- ---------7839 20 PRESIDENT KING 7566 20 7839 MANAGER JONES 7902 20 7566 ANALYST FORD 7369 20 7902 CLERK SMITH Es werde die DB-Operation T1 ausgeführt: update emp set deptno=10 where ename=’JONES’ EMPNO DEPTNO MGR JOB ENAME ---------- ---------- ---------- --------- ---------7839 20 PRESIDENT KING 7566 10 7839 MANAGER JONES 7902 20 7566 ANALYST FORD 7369 20 7902 CLERK SMITH Nach Erreichen des Transaktionsendes besitzt die Relation EMP diese Ausprägung: KAPITEL 3. ERWEITERUNG DES ORACLE7-TRIGGERKONZEPTS 23 exec my_commit EMPNO DEPTNO MGR JOB ENAME ---------- ---------- ---------- --------- ---------7839 20 PRESIDENT KING 7566 10 7839 MANAGER JONES 7902 10 7566 ANALYST FORD 7369 10 7902 CLERK SMITH Die Relation wurde zunächst durch die DB-Operation T1 in den Zustand S1 überführt, was dem Effekt E1 entspricht. Bei der ersten Triggerfeuerung T2 wurde der Angestellte FORD versetzt, Effekt E2 , und die Relation ging in den Zustand S2 über. Als Effekt E3 der zweiten Triggerfeuerung T3 wurde SMITH versetzt und der Zustand S3 erreicht. Die dritte Triggerfeuerung T4 bewirkt keine Veränderung mehr. Der Endzustand S4 ist erreicht. E1E2E3E4S0 S1 S2 S3 S4 T1 T2 T3 T4 3.3 Umsetzung auf Oracle7 Die eben beschriebene Berechnung des Nettoeffekts wird unter Ausnutzung der in Oracle7 vorhandenen Datensatz-Trigger realisiert. Für jede Tabelle, auf der ein verzögerter Trigger definiert ist, werden drei Hilfstabellen erzeugt, die den Mengen I, D und U aus dem EffektTripel entsprechen. Bezeichnet werden sie später mit i <trig name>, d <trig name>, u <trig name>. Jetzt sollen sie der Einfachheit halber Inserted-, Deleted- bzw. UpdatedTabelle genannt werden. Alle drei Tabellen enthalten neben den Spaltenwerten noch einen Eintrag für den Tuple-Identifier des entsprechenden Tupels. Die Hilfstabellen werden Transition-Tables oder ∆-Tabellen genannt. Während der Abarbeitung eines Triggers können diese Tabellen im Triggerrumpf referenziert werden. Weiterhin wird auf jeder betroffenen Tabelle ein Datensatz-Trigger definiert, der dafür sorgen soll, daß die Änderungen des DB-Zustands durch eine Transition in den ∆-Tabellen widergespiegelt werden. Die einzelnen verzögerten Trigger werden in Prozeduren umgewandelt, die von der zusätzlichen Prozedur my commit aufgerufen werden. Der Anwender soll das Transaktionsende mit dem Aufruf dieser Prozedur durch Eingabe des Befehls exec my commit auf SQLPLUS-Ebene angeben. Dabei wird dann die prioritätsgesteuerte Feuerung der verzögerten Trigger vorgenommen. Der Ablauf der prioritätsgesteuerten Feuerung im einzelnen wird in Abschnitt 3.3.3 beschrieben. Zu jeder Tabelle können beliebig viele Trigger erstellt werden. So können auf einer Tabelle mehrere Trigger mit verschiedenen Prioritäten definiert werden. Dies ist derzeit bei den KAPITEL 3. ERWEITERUNG DES ORACLE7-TRIGGERKONZEPTS 24 nicht verzögerten Triggern von Oracle7 nicht möglich. Es folgt die Beschreibung der Umsetzung im einzelnen. 3.3.1 Die ∆-Tabellen und der Hilfstrigger Prinzipiell stehen mehrere Möglichkeiten der Verwaltung der Mengen I, D und U durch Hilfstabellen zur Verfügung. Damit eng verbunden ist das Aussehen des Hilfstriggers, der nach jeder DB-Operation die veränderten Tupel in die Inserted-, Deleted- und UpdatedTabellen einfügen soll. Alternative I Die einfachste Möglichkeit ist die Verwendung von zwei Sätzen ∆-Tabellen pro verzögertem Trigger. Der eine Satz Tabellen, die Hilfs-Inserted-, Hilfs-Deleted- und die HilfsUpdated-Tabelle, wird ständig durch den Hilfstrigger aktualisiert. Das geschieht auch während der Abarbeitung des dazugehörigen verzögerten Triggers. Dabei wird entsprechend den Regeln der Effektberechnung aus Abschnitt 3.2 jedes neu eingefügte, veränderte oder gelöschte Tupel mittels des Hilftriggers in die Hilfs-∆-Tabellen eingetragen. Um den Effekt für den verzögerten Trigger nach seiner Feuerung zu bestimmen, werden vor seiner Feuerung Kopien der Hilfs-∆-Tabellen angelegt. Diese Kopien bilden die eigentlichen ∆-Tabellen. Sie bleiben unverändert während der Triggerabarbeitung und können im Triggerrumpf referenziert werden. Betrachtet man die Regeln der Effektberechnung, so ergibt sich durch die Abarbeitung des Trigger Rl als neuer Transitionseffekt zu diesem Trigger lediglich der Unterschied zwischen den DB-Zuständen Si vor und Si+1 nach der Triggerabarbeitung: Ei+1 Si+1 Si TRl Ek0 i+1 := Eki ◦ Ei+1 gelten für alle Trigger Rk 6=Rl und Ei+1 für den Trigger Rl . Dabei waren Eki die für die Trigger Rk relevanten Effekte im Zustand Si . Falls die Überprüfung, ob in den ∆-Tabellen ein den Trigger Rl auslösendes Ereignis existiert, negativ ausfällt, ist der Effekt Ei+1 für diesen Trigger anschließend leer. Die Berechnung des Effekts Ei+1 für den Trigger Rl muß also auf anfangs leeren Mengen I, D und U erfolgen. Dazu werden nach dem Kopieren der Hilfs-∆-Tabellen alle darin enthaltenen Tupel entfernt. Jetzt wird deutlich, daß die Kopien notwendig wurden, um den auslösenden Effekt bei der Triggerabarbeitung referenzieren zu können. KAPITEL 3. ERWEITERUNG DES ORACLE7-TRIGGERKONZEPTS 25 Da in Oracle7 auf jeder Tabelle nur ein Datensatz-Trigger mit auslösendem Ereignis after insert or delete or update existieren darf, kann für alle auf einer Tabelle existierenden verzögerten Trigger zusammen nur ein Hilfstrigger erzeugt werden, der dann die Hilfs-∆- und ∆-Tabellen für alle diese Trigger verwaltet1 . Für den hier beschriebenen Zweck wäre die Verwendung eines Hilfstriggers pro verzögerten Trigger zwar übersichtlicher, aber bei weitem nicht effizienter, müssen doch statt einem auslösenden Ereignis und einer Triggerbedingung bei der Feuerung des Einfüge-Triggers hier dann für jeden einzelnen verzögerten Trigger diese Bedingungen überprüft werden. Der Hilfstrigger hat nun folgendes Aussehen für einen verzögerten Trigger <trig name> auf einer Tabelle <table name>: create trigger <table_name>_def_trigger after insert or delete or update on <table_name> for each row begin if inserting /* alle neuen Tupel mit Tupel-ID eintragen */ then insert into hilf_i_<trig_name> values :new.tuple, :new.rowID; if updating /* falls Tupel-ID schon in Inserted, dort die neuen Werte einsetzen */ then if row_ID in hilf_i_<trig_name> then update hilf_i_<trig_name> set tuple = :new.tuple where rowID = hilf_i_<trig_name>.rowID; /* falls Tupel-ID schon in Updated, dort die neuen Werte einsetzen */ else if row_ID in hilf_u_<trig_name> then update hilf_u_<trig_name> set new_tuple = :new.tuple where rowID = hilf_u_<trig_name>.rowID; /* sonst in Updated eintragen */ else insert into hilf_u_<trig_name> values :old.tuple, :new.tuple, :new.rowID; 1 In Oracle7.1 kann man mittlerweile mehrere Datensatz-Trigger dieser Art auf einer einzigen Tabelle definieren. KAPITEL 3. ERWEITERUNG DES ORACLE7-TRIGGERKONZEPTS 26 delete from hilf_u_<trig_name> where new_tuple = old_tuple; if deleting /* falls Tupel-ID schon in Inserted, dann dort entfernen */ then if row_ID in hilf_i_<trig_name> then delete from hilf_i_<trig_name> where row_ID = hilf_i_<trig_name>.row_ID; /* falls Tupel-ID schon in Updated, dann dort entfernen und mit den alten Werten in Deleted eintragen */ else if row_ID in hilf_u_<trig_name> then delete from hilf_u_<trig_name> where row_ID = hilf_u_<trig_name>.row_ID; insert into hilf_d_<trig_name> values :old.tuple; /* sonst in Deleted eintragen */ else insert into hilf_d_<trig_name> values :old.tuple; end; Der Nachteil dieser Methode von zwei Sätzen ∆-Tabellen liegt auf der Hand: Es gibt eine große Redundanz der Daten. Außerdem ist der Aufwand mit dem Anlegen von sechs ∆-Tabellen sehr groß. Alternative II Es geht auch mit weniger, wenn man in einer zusätzlichen Spalte je Inserted-, Deletedund Updated-Tabelle noch die Information festhält, ob das Tupel vor Abarbeitung des Triggers schon existierte. Dann kann man nach Triggerabarbeitung einfach alle Tupel mit diesem Vermerk löschen. Doch auch hier treten Probleme auf: Will man die Tupel im Triggerrumpf referenzieren, so muß das Triggersubsystem im Triggerrumpf ergänzen, daß nur die Tupel mit Vermerk gemeint sind. Das sehe z.B. so aus: ... select count(empno) into anzahl from i_<trig_name> where deptno is null and referenzierbar = ’ja’) if anzahl > 0 then ... KAPITEL 3. ERWEITERUNG DES ORACLE7-TRIGGERKONZEPTS 27 Auch der Hilfstrigger müßte nun die neuen Vermerke über die Referenzierbarkeit beachten. Er erhielte folgendes Aussehen: create trigger <table_name>_def_trigger ... if updating then if row_ID in i_<trig_name> and referenzierbar = ’nein’ /* neu */ then update i_<trig_name> set tuple = :new.tuple where rowID = i_<trig_name>.rowID; else if row_ID in i_<trig_name> and referenzierbar = ’ja’ /* neu */ then insert into u_<trig_name> values :old.tuple, :new.tuple, :new.rowID, ’nein’; /* neu */ else if row_ID in u_<trig_name> and referenzierbar = ’nein’ /* neu */ then update u_<trig_name> set new_tuple = :new.tuple where rowID = u_<trig_name>.rowID; else if row_ID in u_<trig_name> and referenzierbar = ’ja’ /* neu */ then insert into u_<trig_name> values :old.tuple, :new.tuple, :new.rowID, ’nein’; /* neu */ else insert into u_<trig_name> values :old.tuple, :new.tuple, :new.rowID, ’nein’; /* neu */ delete from hilf_u_trig1 where new_tuple = old_tuple; ... end; Vor der Feuerung eines Triggers müssen dann natürlich alle Tupel in den dazugehörigen ∆-Tabellen in der Spalte referenzierbar auf ja gesetzt werden. Nach der Triggerabarbeitung werden dann alle Tupel mit dem Vermerk ja in der Spalte referenzierbar gelöscht. KAPITEL 3. ERWEITERUNG DES ORACLE7-TRIGGERKONZEPTS 28 Alternative III Eine dritte Variante geht noch weiter als die zweite: Jetzt soll pro Tabelle, auf der verzögerte Trigger existieren, nur noch ein einziger Satz ∆-Tabellen erstellt werden. In diesem muß dann vermerkt werden, zu welchem Trigger bzw. Triggereffekt welches Tupel gehört. Ein Beispiel soll die Kompliziertheit dieser Methode verdeutlichen: Es seien auf einer Tabelle die verzögerten Trigger R1 , R2 und R3 definiert. In der Ausgangstransition wird ein Tupel t eingefügt. Trigger R1 verändert dieses Tupel nach t0 , Trigger R2 dasselbe Tupel (jetzt t0 ) nach t00 und Trigger R3 nach t000 : Transition T0 TR1 TR2 TR3 Effekt zu Trigger R1 R2 R3 i(t) i(t) i(t) 0 0 u(t,t ) i(t ) i(t0 ) 00 0 00 u(t,t ) u(t ,t ) i(t00 ) u(t,t000 ) u(t0 ,t000 ) u(t00 ,t000 ) Mit dieser Methode kann man zwar einige Eintragungen und Tabellen sparen, der Abarbeitungsmechanismus des Hilfstriggers wird jedoch schwieriger, weil jeweils erkannt werden muß, welches Tupel zu welchem Trigger gehört und ob es referenziert wird. Dabei können mehrere Tupel mit demselben Tuple-Identifier auftreten, die allerdings unterschiedlich behandelt werden müssen. Außerdem müssen immer noch die Referenzierbarkeitsbedingungen eingesetzt werden. Ich habe die erste der drei Methoden gewählt, da sie am einfachsten zu implementieren ist. Der Triggerrumpf muß bis auf die if inserting ... Konstrukte nicht weiter untersucht werden. Sobald ein Tool existiert, das dem Benutzer hilft, verzögerte Trigger nach seinen Wünschen zu erstellen, so daß er sie im einzelnen nicht mehr selbst programmieren muß, sollte das Programm auf die zweite oder dritte Methode umgestellt werden. Dann wird vom Tool die Angabe der Referenzierbarkeitsbedingung übernommen und dem Triggersubsystem bleibt jede Triggerrumpf-Untersuchung erspart. 3.3.2 Speicherung verzögerter Trigger Oracle7 bietet ein prozedurales Konzept an. Dazu gehören die bereits besprochenen Trigger, sowie Prozeduren und Funktionen. Allen gemeinsam ist, daß sie einen PL/SQLBlock enthalten, wie er bereits aus den Triggern bekannt ist. Die Implementation der verzögerten Trigger geht nun so vor sich, daß der Triggerrumpf mit den Variablenund Cursor-Deklarationen komplett in einen Prozedurrumpf hineinkopiert wird. Lediglich die if inserting, if deleting, if updating Konstrukte werden übersetzt: Aus if inserting then ... wird: KAPITEL 3. ERWEITERUNG DES ORACLE7-TRIGGERKONZEPTS 29 select count(*) into i or u or d from i< trig name>; if i or u or d> 0 then ... Die Variable i or u or d enthält die Anzahl der jeweils betroffenen eingefügten, veränderten bzw. gelöschten Tupel. Aus dem verzögerten Trigger gehalt aus Abschnitt 3.1 entstehen dann die ∆-Tabellen i gehalt, u gehalt, d gehalt, die Hilfs-∆-Tabellen hilf i gehalt, hilf u gehalt, hilf d gehalt, der Trigger emp def trigger, die Prozedur my commit sowie aus dem Triggerrumpf die Prozedur gehalt. Hier ein Auszug aus der Prozedur gehalt: create procedure gehalt as i_or_u_or_d number; /* neu */ cursor ins_mgr_cur is select empno from i_gehalt i where exists (select empno from emp where mgr = i.empno and sal > i.sal); ... ins_mgr number(4); ... begin /* nun statt if inserting then */ select count(*) into i_or_u_or_d from i_gehalt; if i_or_u_or_d>0 then open ins_mgr_cur; loop fetch ins_mgr_cur into ins_mgr; exit when ins_mgr_cur%notfound; delete from emp where empno = ins_mgr; end loop; close ins_mgr_cur; ... end; 3.3.3 Prozedur my commit Die Prozedur my commit erfüllt, wie bereits besprochen, eine Doppelfunktion: Zum einen kennzeichnet ihr Aufruf auf SQLPLUS-Ebene mit exec my commit das Transaktionsende, zum anderen übernimmt sie die prioritätsgesteuerte Feuerung der verzögerten Trigger, KAPITEL 3. ERWEITERUNG DES ORACLE7-TRIGGERKONZEPTS 30 also den Aufruf der Prozeduren. Der Ablauf sieht so aus, daß in einer Schleife die Trigger solange gefeuert werden, bis keine Triggerbedingung mehr erfüllt ist. Die Triggerbedingung reduziert sich hier nur auf die Existenz des Auslöseereignisses (vgl. dazu die Syntax in Abschnitt 3.1). Dabei wird nach jedem Ende einer Triggerabarbeitung die Bedingung des Triggers mit der höchsten Priorität geprüft. Ist sie nicht erfüllt, wird die Bedingung des Triggers mit der nächst niedrigeren Priorität untersucht usw. bis ein Trigger gefeuert werden muß. Danach beginnt die Überprüfung wieder beim Trigger mit der höchsten Priorität: procedure my_commit as ... begin loop /* Testen der Triggerbedingung des ersten Triggers */ if condition_trig1 = true then trig1; /* Testen der Triggerbedingung des zweiten Triggers */ else if condition_trig2 = true then trig2; /* Testen der Triggerbedingung des dritten Triggers */ else if condition_trig3 = true then trig3; ... /* Falls keine Triggerbedingung zutrifft: Transaktionsende */ else exit; end loop; end; Die Reihenfolge, in der die Trigger in der Prozedur my commit auftreten, entspricht ihrer Reihenfolge in der Datei, in der die verzögerten Trigger definiert sind. Das ist natürlich noch nicht alles, was in my commit passiert. Wie in Abschnitt 3.3.1 Alternative I beschrieben, müssen vor dem Aufruf der Prozeduren jeweils noch die Inhalte der Hilfs-∆-Tabellen in die eigentlichen noch leeren ∆-Tabellen hineinkopiert werden, dann werden die Hilfs-∆-Tabellen geleert. Ganz am Ende werden dann noch einmal die ∆-Tabellen geleert. Für einen after insert or update-Trigger <trig name> sieht das dann so aus: KAPITEL 3. ERWEITERUNG DES ORACLE7-TRIGGERKONZEPTS procedure my_commit as ins number; del number; upd number; ... begin loop ins=0; del=0; upd=0; /* referenzierbare Tabellen leeren */ delete from i_<trig_name>; delete from d_<trig_name>; delete from u_<trig_name>; /* Hilfstabellen in referenzierbare hineinkopieren */ insert into i_<trig_name> select * from hilf_i_<trig_name>; insert into d_<trig_name> select * from hilf_d_<trig_name>; insert into u_<trig_name> select * from hilf_u_<trig_name>; /* Hilfstabellen leeren */ delete from hilf_i_<trig_name>; delete from hilf_d_<trig_name>; delete from hilf_u_<trig_name>; /* Anzahl der Inserted-Tupel bestimmen */ select count(*) into ins from i_<trig_name>; /* Anzahl der Updated-Tupel bestimmen */ select count(*) into upd from u_<trig_name>; /* falls solche Tupel existieren, ’Trigger’ feuern, also Prozedur aufrufen */ if ins>0 or del>0 or upd>0 then <trig_name>; else ... /* analog bei den weiteren Triggern */ 31 KAPITEL 3. ERWEITERUNG DES ORACLE7-TRIGGERKONZEPTS 32 else exit; /* solange Triggerbedingungen zutreffen */ end loop; /* Leeren der Hilfstabellen von eventuell noch vorhandenen Eintragungen */ delete from hilf_i_<trig_name>; delete from hilf_d_<trig_name>; delete from hilf_u_<trig_name>; ... /* analog bei den weiteren Triggern */ end; 3.3.4 Einschränkungen Bisher ist es nicht möglich, verzögerte Trigger auf Tabellen zu erstellen, die dem Anwender nicht gehören, auch wenn dafür die Zugriffsrechte bestehen. Das liegt daran, daß der Zugriffsmechanismus auf die Spaltennamen und -typen fremder Tabellen über das Data Dictionary nicht gerade plausibel dokumentiert ist. Wenn Möglichkeiten gefunden werden, an diese Informationen heranzukommen, sollte diese Einschränkung aufgehoben werden. Es sind zwar beliebig viele verzögerte Trigger auf einer Tabelle möglich, jedoch kann kein Trigger Ereignisse in zwei Tabellen gleichzeitig überwachen. Hierfür muß eine Lösung mit zwei Triggern gewählt werden. Kapitel 4 Programmbeschreibung Das Programm Trigger, das die verzögerten Trigger aus einer Datei ausliest und dazu die ∆-und Hilfs-∆-Tabellen, Trigger und Prozeduren unter Oracle7 anlegt, ist in Pro*C geschrieben. Oracle7 bietet mehrere Programmiersprachen an, die um Sprachkonstrukte zur Kommunikation mit dem Oracle7 DMBS erweitert wurden. Pro*C ist die Erweiterung der Programmiersprache C (vgl. [O7-Pro*C]). Jedem Pro*C-Befehl wird die Ankündigung EXEC SQL vorangestellt. Der Pro*C-Präprozessor wandelt diese Befehle in normale Funktionenaufrufe um. Dazu werden später beim Linken die Oracle7 Kommunikationsbibliotheken hinzugebunden. Mit dem Standard C-Compiler wird der modifizierte Quellcode übersetzt und vom Linker anschließend zusammengebunden. Das folgende Bild zeigt diesen Ablauf anhand der Beispieldatei foo.pc. foo.pc - die Pro*C-Datei ? Pro*C-Präprozessor ? foo.c - eine C-Datei ? C-Compiler CC ? foo.o - eine Objektdatei ? Linker LD ? foo - das ausführbare Programm 33 KAPITEL 4. PROGRAMMBESCHREIBUNG 34 Der Quelltext von Trigger besteht aus sechs Modulen: • main and oracle.pc Enthält die Funktion main, die den Programmablauf steuert, sowie alle Funktionen, die Pro*C-Befehle enthalten. • head and body.c Enthält die Funktionen, die die Triggerköpfe analysieren und aus den Triggerrümpfen Prozeduren erstellen. • my commit.c Die Funktionen in diesem Modul erzeugen die Prozedur my commit sowie die Datei drop all.sql. • errors.c In diesem Modul sind Funktionen enthalten, die Fehlermeldungen ausgeben. Das folgende Bild gibt einen Überblick über die Abhängigkeiten der Module: main and oracle.pc ? head and body.c ? my commit.c ? errors.c Alle Module haben eine gemeinsame Header-Datei main.h, in der die Deklarationen der Konstanten, globalen Variablen und aller Funktionen enthalten sind. KAPITEL 4. PROGRAMMBESCHREIBUNG 35 Der Programmablauf gestaltet sich in acht Schritten: Anmelden bei Oracle7 ? Lesen des Dateinamens des Trigger-Definitionsfiles ? Triggerköpfe lesen Triggernamen zu Tabellen zuordnen ? Datei drop all.sql anlegen ? ∆-und Hilfs-∆-Tabellen sowie Einfügetrigger anlegen ? Trigger in Prozeduren umwandeln ? my commit erzeugen ? Abmelden bei Oracle7 Es folgt die Beschreibung der Module im einzelnen. 4.1 main.h In dieser Headerdatei werden alle Konstanten, Typ-Definitionen und globalen Variablen angegeben, die von den anderen Modulen benötigt werden. Zusätzlich werden alle Headerfiles für die Einbindung der notwendigen Bibliotheken aufgeführt. Konstanten und Datenstrukturen Im folgenden sind die Konstanten mit ihren Defaultwerten in Klammern aufgeführt: KAPITEL 4. PROGRAMMBESCHREIBUNG TRIGANZ : Anzahl der insgesamt möglichen verzögerten Trigger (10) MAXCOLUMNS : Anzahl der maximal möglichen Spalten in einer Tabelle (10) MAXTRIGLEN : Anzahl der Zeichen in einem verzögerten Trigger (10000) WORTLEN : Länge einer von Leerzeichen getrennten Zeichenkette (100) NAMELEN : Länge eines Trigger- oder Tabellennamens (30) BLOCKLEN : Länge einer Blocks, der an Oracle7 gesandt wird (10000) 36 Die anderen Konstanten geben Fehlercodes für die Funktion show err() aus dem Modul errors an. Wichtige globale Datenstrukturen sind: trig cnt build proc trig table name insert update delete flag pos jmp err Anzahl der verzögerten Trigger Flag für Beachtung der Groß-/Kleinschreibung Struktur mit Informationen zu den verzögerten Triggern: Tabellenname Triggername Flag für Insert-Trigger analog analog un-/benutzt-Flag Position des Triggerrumpfbeginns in der Datei Struktur, die Programmzustand und Adresse für globalen Sprung speichert Der Pro*C-Datentyp VARCHAR, entspricht einer Datenstruktur, in der die Variable arr eine Zeichenkette und len deren Länge enthält. Die Verwendung von Variablen dieses Datentyps anstelle von Character-Arrays wird von Oracle7 bei der Kommunikation vorgeschrieben. 4.2 main and oracle.pc In diesem Modul befinden sich alle Funktionen, die Pro*C-Befehle enthalten1 . Um die SQL-Fehlermeldungen und -Warnungen abfangen zu können, werden die Funktionen sqlerror() und sqlwarn() benötigt. Da sie nichtlokale Sprünge in die main()-Funktion ausführen, müssen sie sich mit dieser im selben Modul befinden. Eine nähere Beschreibung 1 Zur Zeit ist mir kein Weg bekannt, wie mehrere Module von Pro*C präcompiliert werden können, ohne daß später beim Compilieren Probleme auftreten. KAPITEL 4. PROGRAMMBESCHREIBUNG nichtlokaler Sprünge befindet sich in [Illik, 1990] Funktionenbeschreibung oracle login Aufgabe: Baut eine Kommunikationsleitung zu Oracle7 auf, die benötigt wird, um Tabellen, Trigger und Prozeduren in Oracle7 anstelle des Benutzers zu erstellen. Bis zum expliziten Abmelden des Programms bei Oracle7 bleibt die Verbindung bestehen. Das Einlesen des Passworts geschieht ohne Bildschirmecho. Parameter: keine oracle logout Aufgabe: Beendet die Kommunikationsleitung zu Oracle7. Parameter: keine sende block Aufgabe: Schickt eine Zeichenkette an Oracle7. Diese muß eine DB-Operation oder ein PL/SQL-Block sein. Parameter: block - Zeichenkette build trigger and tab Aufgabe: Erzeugt die ∆-und Hilfs-∆-Tabellen sowie den Immediate-Trigger zum automatischen Füllen dieser Tabellen. Das genaue Aussehen des Hilfstriggers ist in Abschnitt 3.3.1 Alternative I in Pseudocode beschrieben. Parameter: keine main Aufgabe: Steuert den auf Seite 35 beschriebenen Programmablauf. Parameter: keine sqlerror Aufgabe: Gibt Oracle7-Fehlermeldungen aus und sorgt für den nichtlokalen Sprung in die Funktion main. Dort wurde der Programmzustand mit setjmp() abgespeichert. Der Sprung bewirkt ein Wiederaufsetzen des Programms mit dem gespeicherten Zustand. Parameter: keine 37 KAPITEL 4. PROGRAMMBESCHREIBUNG 38 sqlwarn Aufgabe: Gibt Oracle7-Warnungen aus und sorgt für den nichtlokalen Sprung in die Funktion main. Parameter: keine 4.3 head and body.c In diesem Modul befinden sich diejenigen Funktionen, die die verzögerten Trigger analysieren und aus den Triggerrümpfen Prozeduren generieren. Die Funktion read trigger() prüft die syntaktische Korrektheit der vom Benutzer spezifizierten verzögerten Trigger. Zunächst wird der Name der Datei mit den verzögerten Triggern in der Variablen filename abgelegt; danach beginnt die syntaktische Analyse des Triggerkopfes. Werden Schlüsselwörter ausgelassen, so wird der Fehler von der Funktion mis err() aus dem Modul errors angezeigt. Wird ein Triggername mehrfach verwendet, so wird dieser Fehler über die Funktion show err() gemeldet. In beiden Fällen wird anschließend die Funktion mit dem Rückgabewert -1 verlassen. Eine Analyse des Triggerrumpfs wird nicht vorgenommen, da dieser, wenn er erst einmal in eine Prozedur umgewandelt wurde, beim Hinübersenden nach Oracle7 dort sowieso auf syntaktische und semantische Korrektheit überprüft wird. Funktionenbeschreibung Exportierte Funktionen sind: read trigger Aufgabe: Prüft die syntaktische Korrektheit der vom Benutzer spezifizierten verzögerten Trigger. Parameter: keine read block Aufgabe: Die Triggerrümpfe werden hier ausgelesen und daraus Prozeduren erstellt. Parameter: keine KAPITEL 4. PROGRAMMBESCHREIBUNG 39 Interne Funktionen sind: read typ Aufgabe: Liest zum aktuell untersuchten Trigger den Typ (inserted, updated, deleted) ein. Parameter: fd - Filedeskriptor if ersetzen Aufgabe: Transformiert die Zeichenketten if inserting, if deleting, if updating in ihre PL/SQL Äquivalente. Aus dem Konstrukt if inserting wird: select count(rowident) into i or u or d from i trigname; if i or u or d > 0. Parameter: trignum - aktueller Trigger fd - Filedeskriptor c - zuletzt gelesenes Zeichen cnt - Filepointerposition wort - neue Zeichenkette get token Aufgabe: Liest aus einer Datei die nächste Zeichenkette bis zum nächsten Leerzeichen, Tabulator- oder Returnzeichen ein. Parameter: fd - Filedeskriptor c - zuletzt gelesenes Zeichen cnt - Filepointerposition 4.4 my commit.c Das Modul my commit enthält zwei Funktionen. Die Funktion drop all def() erstellt die Datei mit den Anweisungen, die alle Tabellen, Trigger und Prozeduren wieder löschen. my commit() baut die namensgleiche Prozedur so zusammen, daß sie die in Abschnitt 3.3.3 beschriebene Gestalt erhält. Aus den in der Datenstruktur trig vorhandenen Informationen stellt drop all def() die Löschoperationen für die Datei drop all.sql zusammen. Ist die Datei bereits vorhanden, wird sie ersetzt, ansonsten neu angelegt. Für den Trigger <trig name> auf der Tabelle <table name> sehe die Datei so aus: drop trigger <table_name>_def_trigger; drop procedure <trig_name>; KAPITEL 4. PROGRAMMBESCHREIBUNG drop drop drop drop drop drop drop 40 table i_<trig_name>; table u_<trig_name>; table d_<trig_name>; table hilf_i_<trig_name>; table hilf_u_<trig_name>; table hilf_d_<trig_name>; procedure my_commit; Dabei wird wie jeder solcher Datensatz-Trigger nur einmal zum Löschen angegeben. Funktionenbeschreibung my commit Aufgabe: Parameter: Erstellt die gleichnamige SQL-Prozedur. keine drop all def Aufgabe: Erstellt die Datei drop all.sql. Parameter: keine 4.5 errors.c Wann immer ein Fehler bei der Erstellung der Trigger auftritt, sei er syntaktischer oder semantischer Natur, springt das Programm wieder an die Stelle, wo der Dateiname eingegeben werden muß. Falls bereits Tabellen, Trigger oder Prozeduren erstellt wurden, müssen diese wieder entfernt werden, bevor ein neuer Versuch unternommen werden kann, verzögerte Trigger zu erstellen. Dazu wird die Datei drop all.sql angelegt, sobald die Namen der verzögerten Trigger und der betroffenen Tabellen dem Programm bekannt sind. Funktionenbeschreibung mis err() Aufgabe: Gibt bei der Fehlermeldung den Namen des fehlerhaften Triggers und das fehlende Token aus. Parameter: trig num - Triggernummer err code - fehlendes Token KAPITEL 4. PROGRAMMBESCHREIBUNG show err() Aufgabe: Gibt bei der Fehlermeldung den Namen des fehlerhaften Triggers und eine Beschreibung des Fehlers aus. Parameter: trig num - Triggernummer err code - Fehlerkonstante 41 Kapitel 5 Benutzerhandbuch 5.1 Triggererstellung Alle verzögerten Trigger, die die Integrität überwachen sollen, müssen zunächst in eine Datei geschrieben werden. Die Reihenfolge in der Datei gibt dabei implizit die Reihenfolge der Feuerung an. Damit wird eine prioritätsgesteuerte Feuerung ermöglicht. Prinzipiell sind beliebig viele verzögerte Trigger möglich. Tatsächlich wird die maximale Anzahl der Trigger in der Konstanten TRIGANZ festgesetzt. Hier kann der Anwender das Programm also seinen Bedürfnissen anpassen. Die Syntax der verzögerten Trigger entspricht weitestgehend der der Trigger in Oracle7 (vgl. dazu Abschnitt 3.1). Neu ist das Schlüsselwort deferred zur Kennzeichnung der verzögerten Trigger. Das declare ist hier optional. Jeder Trigger muß in der Datei mit einem Slash ’/’ in der Zeile nach dem letzten end; abgeschlossen werden. Außer dem Slash darf nichts anderes in dieser Zeile stehen. Ansonsten sind innerhalb des Triggers beliebig viele Leerzeichen und Tabulatoren zwischen den einzelnen Wörtern möglich. Die Reihenfolge, in der im Triggerrumpf die auslösenden Ereignisse angegeben werden, ist beliebig: ... after insert or update ... after delete or insert ... after update or delete or insert Ein Ereignis darf nicht mehrfach angegeben werden. Der Triggerrumpf muß ein PL/SQL-Programm enthalten, wie es auch in Prozeduren vorkommen kann. Als einzige Abweichung sind die Konstrukte 42 KAPITEL 5. BENUTZERHANDBUCH 43 if inserting if deleting if updating erlaubt, um eine Verzweigung in Abhängigkeit vom auslösenden Ereignis zu ermöglichen. Eine Kombination wie if inserting or updating ist jedoch nicht gestattet. Es ist bei der Triggererstellung darauf zu achten, daß keine Endlos-Zyklen entstehen, da diese nicht erkannt werden. Weiterhin ist die Reihenfolge der Trigger bzw. die Priorität zu beachten, damit auch der gewünschte Effekt erzielt wird. Das Vertauschen der Reihenfolge zweier Trigger kann sonst völlig unterschiedliche Ergebnisse erzielen. Die beiden folgenden Trigger bilden dafür ein Beispiel: Der Trigger pres gehalt bewirkt, daß das Gehalt eines neuen Präsidenten mindesten doppelt so hoch ist wie das seiner Manager. Der Trigger manager gibt jedem neuen Manager eine zwanzigprozentige Gehaltserhöhung. create deferred trigger pres_gehalt after insert or update on emp declare gehalt number(7,2); mgr_gehalt number(7,2); begin if inserting then /* Gehalt des Chefs lesen */ select sal into gehalt from i_pres_gehalt where job = ’PRESIDENT’; /* maximales Managergehalt lesen */ select max(sal) into mgr_gehalt from emp where job = ’MANAGER’; /* Falls Chefgehalt zu gering: anheben ! */ if gehalt is not null and gehalt < 2*mgr_gehalt then update emp set sal = 2*mgr_gehalt where job = ’PRESIDENT’; end if; end if; KAPITEL 5. BENUTZERHANDBUCH if updating then /* Neues Chefgehalt lesen */ select new_sal into gehalt from u_pres_gehalt where new_job = ’PRESIDENT’; /* maximales Managergehalt lesen */ select max(sal) into mgr_gehalt from emp where job = ’MANAGER’; /* Chefgehalt anheben, falls niedriger */ if gehalt is not null and gehalt < 2*mgr_gehalt then update emp set sal = 2*mgr_gehalt where job = ’PRESIDENT’; end if; end if; end; create deferred trigger manager after update on emp declare /* liest Angestelltennummer neuer Manager */ cursor upd_mgr_cur is select new_empno from u_manager where new_job = ’MANAGER’ and old_job != ’MANAGER’; upd number; begin open upd_mgr_cur; loop fetch upd_mgr_cur into upd; exit when upd_mgr_cur%notfound; /* Setze Gehalt neuer Manager um 20% herauf */ update emp set sal = sal*1.20 where empno = upd; end loop; close upd_mgr_cur; end; 44 KAPITEL 5. BENUTZERHANDBUCH 45 Der Anfangszustand sehe wie folgt aus: EMPNO ENAME JOB MGR SAL DEPTNO ---------- ---------- --------- ---------- --------- ---------7839 7499 7698 7782 KING ALLEN BLAKE CLARK PRESIDENT SALESMAN MANAGER MANAGER 7698 7839 7839 5000 2500 2085 2450 20 30 30 20 Die DB-Operationen seien die folgenden: update emp set job=’MANAGER’ where ename=’ALLEN’; update emp set sal=5300 where ename=’KING’; exec my_commit Hat der Trigger pres gehalt die höhere Priorität, so sieht der Endzustand nach Abarbeitung beider Trigger so aus: EMPNO ENAME JOB MGR SAL DEPTNO ---------- ---------- --------- ---------- --------- ---------7839 7499 7698 7782 KING ALLEN BLAKE CLARK PRESIDENT MANAGER MANAGER MANAGER 7698 7839 7839 5300 3000 2085 2450 20 30 30 20 Offenbar verdient der Präsident nun nicht doppelt so viel wie Allen. Wenn hingegen der Trigger manager die höhere Priorität erhält, sieht das Ergebnis anders aus: EMPNO ENAME JOB MGR SAL DEPTNO ---------- ---------- --------- ---------- --------- ---------7839 7499 7698 7782 KING ALLEN BLAKE CLARK PRESIDENT MANAGER MANAGER MANAGER 7698 7839 7839 6000 3000 2085 2450 20 30 30 20 KAPITEL 5. BENUTZERHANDBUCH 5.2 46 Programmbedienung Das Programm, das die nötigen Tabellen, Trigger und Prozeduren zu den verzögerten Trigger erstellt, heißt Trigger. Nach dem Programmstart sind Benutzername und Passwort einzugeben, damit das Programm eine Verbindung zur Oracle7-Datenbank unter der Kennung des Benutzers aufbauen kann. Diese Verbindung ist nötig, um die Tabellen, Trigger und Prozeduren erstellen zu können. Sobald die Kommunikation aufgebaut ist, wird die Eingabe des Namens der Datei verlangt, in der die verzögerten Trigger stehen. Als nächstes werden die Triggerköpfe analysiert. Zuerst wird eine Datei drop all.sql erzeugt, die die nötigen SQL-Befehle enthält, um die von Trigger erzeugten Tabellen, Trigger und Prozeduren löschen. Wenn die verzögerten Trigger gelöscht oder verändert werden sollen, muß man diese Befehle eingeben oder die Datei mit @drop all von SQLPLUS aus einladen. Dies ist auch nötig, wenn sich ein Fehler in einen verzögerten Trigger eingeschlichen hat oder wenn noch verzögerte Trigger mit gleichem Namen existieren. Soll das Programm abgebrochen werden, wenn nach dem Dateinamen gefragt wird, so ist das Ausrufezeichen ! einzugeben. Trigger erzeugt pro verzögerten Trigger <trig name> die folgenden Tabellen: i_<trig_name> d_<trig_name> u_<trig_name> hilf_i_<trig_name> hilf_d_<trig_name> hilf_u_<trig_name> und die Prozedur <trig name>. Weiterhin werden die Prozedur my commit und pro Tabelle <table name>, auf der ein verzögerter Trigger existiert, die (after insert or delete or update) Datensatz-Trigger <table name> def trigger erzeugt. Wenn das Programm fehlerfrei beendet wurde, sind die verzögerten Trigger aktiviert. Die Transaktion, die die Trigger überwachen sollen, kann nun durchgeführt werden. Das Transaktionsende muß dann durch den Aufruf der Prozedur my commit gekennzeichnet werden. Unter SQLPLUS geschieht dies durch den Befehl exec my commit. Nach der Triggerabarbeitung meldet sich SQLPLUS mit der Meldung procedure successfully completed zurück. Jetzt können entweder eine neue Transaktion begonnen oder die verzögerten Trigger entfernt werden. KAPITEL 5. BENUTZERHANDBUCH 47 Sollte es einmal nötig sein, den Quellcode von Trigger neu zu compilieren, so kann das mit dem Makefile prok.mk geschehen. Der Präprozessor benötigt dazu aus dem Makefile einen Benutzernamen und das dazugehörige Passwort. Die Verwendung von SCOTT/TIGER reicht dabei völlig. Leider ist das Makefile nicht optimal, da ein extrem großes ausführbares Programm erstellt wird. Hier wäre eine Verbesserung angebracht, die aber eine gute Dokumentation von Pro*C voraussetzt. Der UNIX-Befehl zum Compilieren aller Module und zum anschließenden Zusammenlinken lautet: make -f proc main and oracle Kapitel 6 Erweiterungsmöglichkeiten Die Einschränkungen wurden bereits in den vorangegangenen Kapiteln beschrieben. Sie gilt es in der Zukunft auszumerzen – vielleicht auch, wenn Oracle7 bessere Zugriffsmöglichkeiten auf das Data Dictionary gestattet oder die vorhandenen besser dokumentiert. Das wohl größte Manko ist der hohe Speicherplatzbedarf, bedingt durch die sechs Tabellen, die für jeden Trigger angelegt werden, und die damit verbundene Redundanz. Eine schöne Weiterentwicklung wäre ein Tool, das dem Anwender das Entwerfen der verzögerten Trigger erleichtert. Um die Syntax und Variablen- bzw. Cursor-Deklarationen bräuchte sich der Benutzer dann nicht mehr zu kümmern, er gibt nur die auslösenden Ereignisse, die Bedingung und die Aktion an, und um das Zusammenfügen zu einem verzögerten Trigger kümmert sich das Tool. Dann sollte man auch die in Kapitel 3.3.1 vorgeschlagenen Methoden implementieren, die die Trigger mit weniger Tabellen und weniger Redundanz realisieren. Weiterhin kann es wünschenswert sein, wenn ein verzögerter Trigger auch auf mehreren Tabellen erstellt werden könnte. Bei der Erstellung von Triggern auf fremden Tabellen stellt sich zur Zeit noch das Problem des Zugriffs auf die Spaltennamen und -typen der fremden Tabellen. 48 Literaturverzeichnis [Ceri and Widom, 1990] Ceri, S. and Widom, J. (1990). Deriving production rules for constraint maintenance. In McLeod, D., Sacks-Davis, R., and Schek, H., editors, Proceedings of the 16th Int. Conf. on Very Large Data Bases - 1990, pages 566–577. Morgan Kaufmann Publishers. [Gehani and Jagadish, 1991] Gehani, N. and Jagadish, H. (1991). Ode as an active database: Constraints and triggers. In Lohmann, G. M., Sernadas, A., and Camps, R., editors, Proceedings of the 17th Int. Conf. on Very Large Data Bases - 1991, pages 327–336. Morgan Kaufmann Publishers. [Gertz and Lipeck, 1993] Gertz, M. and Lipeck, U. W. (1993). Deriving integrity maintaining triggers from transition graphs. In Proceedings Ninth International Conference on Data Engineering, pages 22–29. IEEE Computer Society Press. [Hanson and Widom, 1992] Hanson, E. N. and Widom, J. (1992). An overview of production rules in database systems. Technical Report CIS-TR-92-031, University of Florida. [Illik, 1990] Illik, J. A. (1990). Programmieren in C unter UNIX. Sybex, Düsseldorf. [O7-AppDev] Oracle7: Application Developer’s Guide. Part No. 6695-70-1292, 1992. [O7-PL/SQL] Oracle7: PL/SQL User’s Guide and Reference. Part No. 800-20-1292, 1992. [O7-Pro*C] Oracle7: Pro*C Supplement to the Oracle Precompilers Guide. Part No. 5452-15-1292, 1992. [Widom, 1992] Widom, J. (1992). Starburst rule system user’s guide. Technical report, IBM Almaden Research Center, San Jose, CA. [Widom, 1993] Widom, J. (1993). The starburst active database rule system. Technical report, Department of Computer Science, Stanford, CA. 49