FirebirdSQL logo

Abfangen und Behandeln von Fehlern

Firebird verfügt über ein nützliches Lexikon von PSQL-Anweisungen und -Ressourcen zum Abfangen von Fehlern in Modulen und deren Behandlung.Firebird verwendet integrierte Ausnahmen, die bei Fehlern ausgelöst werden, die beim Arbeiten mit DML- und DDL-Anweisungen auftreten.

Im PSQL-Code werden Ausnahmen mit der WHEN-Anweisung behandelt.Das Behandeln einer Ausnahme im Code beinhaltet entweder das Beheben des Problems vor Ort oder das Überwinden des Problems.Bei beiden Lösungen kann die Ausführung fortgesetzt werden, ohne dass eine Ausnahmenachricht an den Client zurückgegeben wird.

Eine Ausnahme führt dazu, dass die Ausführung im aktuellen Block beendet wird.Anstatt die Ausführung an die END-Anweisung zu übergeben, bewegt sich die Prozedur ausgehend von dem Block, in dem die Ausnahme abgefangen wurde, durch Ebenen verschachtelter Blöcke nach außen und sucht nach dem Code des Handlers, der diese Ausnahme „kennt“.Es stoppt die Suche, wenn es die erste WHEN-Anweisung findet, die diese Ausnahme behandeln kann.

Systemausnahmen

Eine Ausnahme ist eine Nachricht, die generiert wird, wenn ein Fehler auftritt.

Alle von Firebird behandelten Ausnahmen haben vordefinierte numerische Werte für Kontextvariablen (Symbole) und damit verbundene Textnachrichten.Fehlermeldungen werden standardmäßig in Englisch ausgegeben.Es sind lokalisierte Firebird-Builds verfügbar, bei denen Fehlermeldungen in andere Sprachen übersetzt werden.

Vollständige Auflistungen der Systemausnahmen finden Sie in Anhang B: Ausnahmecodes und Meldungen:

docnext count = 5

Benutzerdefinierte Ausnahmen

Benutzerdefinierte Ausnahmen können in der Datenbank als persistente Objekte deklariert und im PSQL-Code aufgerufen werden, um bestimmte Fehler zu signalisieren;B. um bestimmte Geschäftsregeln durchzusetzen.Eine benutzerdefinierte Ausnahme besteht aus einem Bezeichner und einer Standardnachricht von 1021 Byte.Weitere Informationen finden Sie unter CREATE EXCEPTION.

EXCEPTION

Verwendet für

Eine benutzerdefinierte Ausnahme auslösen oder eine Ausnahme erneut auslösen

Verfügbar in

PSQL

Syntax
EXCEPTION [
    exception_name
    [ custom_message
    | USING (<value_list>)]
  ]

<value_list> ::= <val> [, <val> ...]
Table 1. EXCEPTION-Anweisungsparameter
Argument Beschreibung

exception_name

Name der Ausnahme

custom_message

Alternativer Nachrichtentext, der an die Aufruferschnittstelle zurückgegeben wird, wenn eine Ausnahme ausgelöst wird.Die maximale Länge der Textnachricht beträgt 1.021 Byte

val

Wertausdruck, der Parameter-Slots im Ausnahmenachrichtentext ersetzt

Eine Anweisung EXCEPTION löst die benutzerdefinierte Ausnahme mit dem angegebenen Namen aus.Ein alternativer Nachrichtentext von bis zu 1.021 Byte kann optional den Standardnachrichtentext der Ausnahme überschreiben.

Die Standardausnahmenachricht kann Slots für Parameter enthalten, die beim Auslösen einer Ausnahme gefüllt werden können.Um Parameterwerte an eine Ausnahme zu übergeben, verwenden Sie die USING-Klausel.Betrachten wir in der Reihenfolge von links nach rechts, dass jeder Parameter, der in der Anweisung zum Auslösen von Ausnahmen als “the Nth” übergeben wird, mit N beginnend bei 1:

  • Wenn der Nte Parameter nicht übergeben wird, wird sein Slot nicht ersetzt

  • Wird ein NULL Parameter übergeben, wird der Slot durch den String “*** null ***” ersetzt

  • Werden mehr Parameter übergeben, als in der Ausnahmemeldung definiert sind, werden die überzähligen ignoriert

  • Die maximale Anzahl von Parametern beträgt 9

  • Die maximale Nachrichtenlänge einschließlich Parameterwerten beträgt 1053 Byte

Note

Der Statusvektor wird durch diese Codekombination isc_except, <Exception number>, isc_formatted_exception, <formatted Exception message>, <Exception parameters> generiert.

Der verwendete Fehlercode (isc_formatted_exception) wurde in Firebird 3.0 eingeführt, daher muss der Client mindestens Version 3.0 sein oder mindestens die firebird.msg ab Version 3.0 oder höher verwenden, um den Statusvektor in einen String zu übersetzen.

Warning

Wenn die message eine Parameter-Slot-Nummer enthält, die größer als 9 ist, werden die zweite und die nachfolgenden Ziffern als Literaltext behandelt.Zum Beispiel wird @10 als Slot 1 interpretiert, gefolgt von einem Literal ``0’.

Als Beispiel:

CREATE EXCEPTION ex1
  'something wrong in @ 1 @ 2 @ 3 @ 4 @ 5 @ 6 @ 7 @ 8 @ 9 @ 10 @ 11';
SET TERM ^;
EXECUTE BLOCK AS
BEGIN
  EXCEPTION ex1 USING ( 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i' );
END^

Dies erzeugt die folgende Ausgabe

Statement failed, SQLSTATE = HY000
exception 1
-EX1
-something wrong in abcdefghi a0 a1

Ausnahmen können in einer [fblangref40-psql-when-de]-Anweisung behandelt werden.Wenn eine Ausnahme in einem Modul nicht behandelt wird, werden die Auswirkungen der in diesem Modul ausgeführten Aktionen aufgehoben und das aufrufende Programm empfängt die Ausnahme (entweder den Standardtext oder den benutzerdefinierten Text).

Innerhalb des Ausnahmebehandlungsblocks — und nur darin — kann die abgefangene Ausnahme erneut ausgelöst werden, indem die EXCEPTION-Anweisung ohne Parameter ausgeführt wird.Wenn er sich außerhalb des Blocks befindet, hat der erneut ausgelöste EXCEPTION-Aufruf keine Wirkung.

Note

Custom exceptions are stored in the system table RDB$EXCEPTIONS.

EXCEPTION-Beispiele

  1. Auslösen einer Ausnahme bei einer Bedingung in der gespeicherten Prozedur SHIP_ORDER:

    CREATE OR ALTER PROCEDURE SHIP_ORDER (
      PO_NUM CHAR(8))
    AS
      DECLARE VARIABLE ord_stat  CHAR(7);
      DECLARE VARIABLE hold_stat CHAR(1);
      DECLARE VARIABLE cust_no   INTEGER;
      DECLARE VARIABLE any_po    CHAR(8);
    BEGIN
      SELECT
        s.order_status,
        c.on_hold,
        c.cust_no
      FROM
        sales s, customer c
      WHERE
        po_number = :po_num AND
        s.cust_no = c.cust_no
      INTO :ord_stat,
           :hold_stat,
           :cust_no;
    
      IF (ord_stat = 'shipped') THEN
        EXCEPTION order_already_shipped;
      /* Other statements */
    END
  2. Eine Ausnahme bei einer Bedingung auslösen und die ursprüngliche Nachricht durch eine alternative Nachricht ersetzen:

    CREATE OR ALTER PROCEDURE SHIP_ORDER (
      PO_NUM CHAR(8))
    AS
      DECLARE VARIABLE ord_stat  CHAR(7);
      DECLARE VARIABLE hold_stat CHAR(1);
      DECLARE VARIABLE cust_no   INTEGER;
      DECLARE VARIABLE any_po    CHAR(8);
    BEGIN
      SELECT
        s.order_status,
        c.on_hold,
        c.cust_no
      FROM
        sales s, customer c
      WHERE
        po_number = :po_num AND
        s.cust_no = c.cust_no
      INTO :ord_stat,
           :hold_stat,
           :cust_no;
    
      IF (ord_stat = 'shipped') THEN
        EXCEPTION order_already_shipped
          'Order status is "' || ord_stat || '"';
      /* Other statements */
    END
  3. Verwenden einer parametrisierten Ausnahme:

    CREATE EXCEPTION EX_BAD_SP_NAME
      'Name of procedures must start with' '@ 1' ':' '@ 2' '' ;
    ...
    CREATE TRIGGER TRG_SP_CREATE BEFORE CREATE PROCEDURE
    AS
      DECLARE SP_NAME VARCHAR(255);
    BEGIN
      SP_NAME = RDB$GET_CONTEXT ('DDL_TRIGGER' , 'OBJECT_NAME');
      IF (SP_NAME NOT STARTING 'SP_') THEN
        EXCEPTION EX_BAD_SP_NAME USING ('SP_', SP_NAME);
    END
  4. Logging an error and re-throwing it in the WHEN block:

    CREATE PROCEDURE ADD_COUNTRY (
      ACountryName COUNTRYNAME,
      ACurrency VARCHAR(10))
    AS
    BEGIN
      INSERT INTO country (country,
                           currency)
      VALUES (:ACountryName,
              :ACurrency);
      WHEN ANY DO
      BEGIN
        -- write an error in log
        IN AUTONOMOUS TRANSACTION DO
          INSERT INTO ERROR_LOG (PSQL_MODULE,
                                 GDS_CODE,
                                 SQL_CODE,
                                 SQL_STATE)
          VALUES ('ADD_COUNTRY',
                  GDSCODE,
                  SQLCODE,
                  SQLSTATE);
        -- Re-throw exception
        EXCEPTION;
      END
    END

WHEN …​ DO

Verwendet für

Eine Ausnahme abfangen und den Fehler behandeln

Verfügbar in

PSQL

Syntax
WHEN {<error> [, <error> ...] | ANY}
DO <compound_statement>

<error> ::=
  { EXCEPTION exception_name
  | SQLCODE number
  | GDSCODE errcode
  | SQLSTATE sqlstate_code }
Table 1. WHEN …​ DO-Anweisungsparameter
Argument Beschreibung

exception_name

Name der Ausnahme

number

SQLCODE-Fehlercode

errcode

Symbolischer GDSCODE-Fehlername

sqlstate_code

String-Literal mit dem SQLSTATE-Fehlercode

compound_statement

Eine einzelne Anweisung oder ein Block von Anweisungen

Die Anweisung WHEN …​ DO wird verwendet, um Fehler und benutzerdefinierte Ausnahmen zu behandeln.Die Anweisung erfasst alle Fehler und benutzerdefinierten Ausnahmen, die nach dem Schlüsselwort WHEN aufgeführt sind.Wenn WHEN das Schlüsselwort ANY folgt, fängt die Anweisung jeden Fehler oder jede benutzerdefinierte Ausnahme ab, auch wenn sie bereits in einer WHEN-Anweisung weiter oben im Block behandelt wurden.

Der WHEN …​ DO-Block muss sich am Ende eines Anweisungsblocks befinden, vor der Anweisung END des Blocks.

Auf das Schlüsselwort DO folgt eine Anweisung oder ein Anweisungsblock innerhalb eines BEGIN …​ END-Wrappers, der die Ausnahme behandelt.Die Kontextvariablen SQLCODE, GDSCODE und SQLSTATE stehen im Kontext dieser Anweisung oder dieses Blocks zur Verfügung.Die Funktion RDB$ERROR kann verwendet werden, um den SQLCODE, GDSCODE, SQLSTATE, den benutzerdefinierten Ausnahmenamen und die Ausnahmemeldung abzurufen.Die Anweisung EXCEPTION ohne Parameter kann auch in diesem Kontext verwendet werden, um den Fehler oder die Ausnahme erneut zu werfen.

Bezüglich GDSCODE

Das Argument für die Klausel WHEN GDSCODE ist der symbolische Name, der der intern definierten Ausnahme zugeordnet ist, z.B. grant_obj_notfound für den GDS-Fehler 335544551.

In einer Anweisung oder einem Anweisungsblock der DO-Klausel wird eine GDSCODE-Kontextvariable verfügbar, die den numerischen Code enthält.Dieser numerische Code ist erforderlich, wenn Sie eine GDSCODE-Ausnahme mit einem gezielten Fehler vergleichen möchten.Um ihn mit einem bestimmten Fehler zu vergleichen, müssen Sie einen numerischen Wert verwenden, zum Beispiel 335544551 für grant_obj_notfound.

Ähnliche Kontextvariablen sind für SQLCODE und SQLSTATE verfügbar.

Die Anweisung oder der Block WHEN …​ DO wird nur ausgeführt, wenn eines der von seinen Bedingungen betroffenen Ereignisse zur Laufzeit eintritt.Wenn die Anweisung WHEN …​ DO ausgeführt wird, wird die Ausführung auch dann fortgesetzt, als ob kein Fehler aufgetreten wäre: Der Fehler oder die benutzerdefinierte Ausnahme beendet weder die Operationen des Triggers oder der gespeicherten Prozedur noch setzt sie diese zurück.

Wenn jedoch die WHEN …​ DO-Anweisung oder der Block nichts zur Behandlung oder Behebung des Fehlers tut, wird die DML-Anweisung (SELECT, INSERT, UPDATE, DELETE, MERGE), die den Fehler verursacht hat, error wird zurückgesetzt und keine der Anweisungen darunter im selben Anweisungsblock wird ausgeführt.

Important
  1. Wenn der Fehler nicht durch eine der DML-Anweisungen (SELECT, INSERT, UPDATE, DELETE, MERGE) verursacht wird, wird der gesamte Anweisungsblock zurückgesetzt, nicht nur der, der den Fehler verursacht hat ein Fehler.Alle Operationen in der WHEN …​ DO-Anweisung werden ebenfalls zurückgesetzt.Die gleiche Einschränkung gilt für die Anweisung EXECUTE PROCEDURE.Lesen Sie eine interessante Diskussion des Phänomens im Firebird Tracker-Ticket firebird#4803.

  2. In auswählbaren gespeicherten Prozeduren bleiben Ausgabezeilen, die bereits in früheren Iterationen einer `FOR SELECT …​ DO …​ SUSPEND'-Schleife an den Client übergeben wurden, für den Client verfügbar, wenn anschließend beim Abrufen von Zeilen eine Ausnahme ausgelöst wird.

Anwendungsbereiche einer WHEN …​ DO-Anweisung

Eine Anweisung WHEN …​ DO fängt Fehler und Ausnahmen im aktuellen Anweisungsblock ab.Es fängt auch ähnliche Ausnahmen in verschachtelten Blöcken ab, wenn diese Ausnahmen nicht in ihnen behandelt wurden.

Alle Änderungen, die vor der Anweisung vorgenommen wurden, die den Fehler verursacht hat, sind für eine WHEN …​ DO-Anweisung sichtbar.Wenn Sie jedoch versuchen, sie in einer autonomen Transaktion zu protokollieren, sind diese Änderungen nicht verfügbar, da die Transaktion, bei der die Änderungen stattfanden, zu dem Zeitpunkt, zu dem die autonome Transaktion gestartet wird, nicht festgeschrieben ist.Das untere Beispiel 4 zeigt dieses Verhalten.

Tip

Bei der Behandlung von Ausnahmen ist es manchmal wünschenswert, die Ausnahme zu behandeln, indem eine Protokollnachricht geschrieben wird, um den Fehler zu markieren und die Ausführung über den fehlerhaften Datensatz hinaus fortsetzen zu lassen.Logs können in reguläre Tabellen geschrieben werden, aber dabei gibt es ein Problem: Die Log-Records werden “verschwinden”, wenn ein nicht behandelter Fehler dazu führt, dass das Modul nicht mehr ausgeführt wird und ein Rollback durchgeführt wird.Die Verwendung von external tables kann hier sinnvoll sein, da die Daten, die in diese geschrieben werden, transaktionsunabhängig sind.Die verknüpfte externe Datei ist weiterhin vorhanden, unabhängig davon, ob der Gesamtprozess erfolgreich ist oder nicht.

Beispiele für WHEN…​DO

  1. Ersetzen des Standardfehlers durch einen benutzerdefinierten Fehler:

    CREATE EXCEPTION COUNTRY_EXIST '';
    SET TERM ^;
    CREATE PROCEDURE ADD_COUNTRY (
      ACountryName COUNTRYNAME,
      ACurrency VARCHAR(10) )
    AS
    BEGIN
      INSERT INTO country (country, currency)
        VALUES (:ACountryName, :ACurrency);
    
      WHEN SQLCODE -803 DO
        EXCEPTION COUNTRY_EXIST 'Country already exists!';
    END^
    SET TERM ^;
  2. Einen Fehler protokollieren und erneut in den WHEN-Block werfen:

    CREATE PROCEDURE ADD_COUNTRY (
      ACountryName COUNTRYNAME,
      ACurrency VARCHAR(10) )
    AS
    BEGIN
      INSERT INTO country (country,
                           currency)
      VALUES (:ACountryName,
              :ACurrency);
      WHEN ANY DO
      BEGIN
        -- write an error in log
        IN AUTONOMOUS TRANSACTION DO
          INSERT INTO ERROR_LOG (PSQL_MODULE,
                                 GDS_CODE,
                                 SQL_CODE,
                                 SQL_STATE,
                                 MESSAGE)
          VALUES ('ADD_COUNTRY',
                  GDSCODE,
                  SQLCODE,
                  SQLSTATE,
                  RDB$ERROR(MESSAGE));
        -- Re-throw exception
        EXCEPTION;
      END
    END
  3. Behandeln mehrerer Fehler in einem WHEN-Block

    ...
    WHEN GDSCODE GRANT_OBJ_NOTFOUND,
    	 GDSCODE GRANT_FLD_NOTFOUND,
    	 GDSCODE GRANT_NOPRIV,
    	 GDSCODE GRANT_NOPRIV_ON_BASE
    DO
    BEGIN
      EXECUTE PROCEDURE LOG_GRANT_ERROR(GDSCODE,
        RDB$ERROR(MESSAGE);
      EXIT;
    END
    ...
  4. Abfangen von Fehlern mit dem SQLSTATE-Code

    EXECUTE BLOCK
    AS
      DECLARE VARIABLE I INT;
    BEGIN
      BEGIN
        I = 1/0;
        WHEN SQLSTATE '22003' DO
          EXCEPTION E_CUSTOM_EXCEPTION
            'Numeric value out of range.';
        WHEN SQLSTATE '22012' DO
          EXCEPTION E_CUSTOM_EXCEPTION
            'Division by zero.';
        WHEN SQLSTATE '23000' DO
          EXCEPTION E_CUSTOM_EXCEPTION
           'Integrity constraint violation.';
      END
    END