[cx_Oracle入門](第12回) DB例外処理

# 連載目次
[連載：cx_Oracle入門　目次](https://qiita.com/nakaie/items/df170c3fa6895cf1b44d)

# 検証環境
- Oracle Cloud利用
- Oracle Linux 7.7 (VM.Standard2.1)
- Python 3.6
- cx_Oracle 8.0
- Oracle Database 19.5 (ATP, 1OCPU)
- Oracle Instant Client 18.5

# cx_Oracle.DatabaseError
cx_Oracle.DatabaseErrorという例外が基本的なcx_Oracle利用時に発生した問題に対する例外となります。cx_Oracle.DatabaseError自体は、Python標準のErrorのサブクラスであるcx_Oracle.Errorのサブクラスとなっています。他にも多くのcx_Oracleの例外がありますが、それらはこれらのいずれかの例外のサブクラスとして定義されています。基本、内容を問わずcx_Oracleの例外をまとめてハンドリングしたい場合は、cx_Oracle.DatabaseErrorを使用します。


```python:sample12a.py
import cx_Oracle

USERID = "admin"
PASSWORD = "FooBar"
DESTINATION = "atp1_aaa"
SQL = """
        select object_id, owner, object_name, object_type
          from all_objects
         order by object_id
         fetch first 5 rows only
"""

with cx_Oracle.connect(USERID, PASSWORD, DESTINATION) as connection:
        cursor = connection.cursor()
        cursor.execute(SQL)
        for row in cursor:
                print(row)
        cursor.close()
```

```実行例
$ python sample12a.py
Traceback (most recent call last):
  File "sample12a.py", line 13, in <module>
    with cx_Oracle.connect(USERID, PASSWORD, DESTINATION) as connection:
cx_Oracle.DatabaseError: ORA-12154: TNS:could not resolve the connect identifier specified
```

sample12a.pyでは、4行目のDESTINATION変数の内容に存在しないTNS接続子を指定しています。ですので実行すると必ず接続エラーになります。このサンプルのように、コーディング上、特段のエラーハンドリングを行っていない場合は、cx_Oracle.DatabaseErrorの例外が発生します。例外のメッセージ内容は、エラー内容に該当するOracle Databaseのエラー番号(ORA-xxxxx、実行例の「ORA-12154」)とエラー番号に対応したエラーメッセージ(実行例の「TNS:could not resolve the connect identifier specified」)です。このサンプル実行時に環境変数NLS_LANGを指定していないので、エラーメッセージは英語になっています。日本語のエラーメッセージを受け取りたい場合は、NLS_LANGを設定してください。


# 例外の一覧
cx_Oracle.Errorとcx_Oracle.DatabaseError以外にも、個別の事象に応じた例外や、DB APIで規定されている例外が定義されています。

| 例外名 | 説明 |
|:-----------------|:------------------|
| cx_Oracle.InterfaceError | cx_Oracleのインターフェースを利用している際の問題に関する例外です。一例としてはcx_OralceのAPIの利用方法を誤っている場合などに発生します。 |
| cx_Oracle.DataError | 0除算や桁あふれなど、データ内容に問題がある場合に発生します。 |
| cx_Oracle.OperationalError | ORA-600のようなDB内部のエラーや、ORA-3135のような通信エラーなどの場合に発生します。 |
| cx_Oracle.IntegrityError | 参照整合性制約違反のようなデータの整合性に関する問題がある場合に発生します。 |
| cx_Oracle.InternalError | 内部エラーの際に発生します。ORA-600など事前に定義されている内部エラーはcx_Oracle.OperationalErrorになるので、これらのエラーコードにならない内部の問題が該当します。一例としては無効になったカーソルにアクセスした場合などに発生します。 |
| cx_Oracle.ProgrammingError | プログラミング上の問題に関する例外です。一例としては、発行するSQL文に問題がある場合などに発生します。 |
| cx_Oracle.NotSupportedError | 存在しないcx_Oralceのメソッドをコールしたような場合に発生します。 |
| cx_Oracle.Warning | DB APIに存在するため定義はされていますが、cx_Oracleでは実質的に利用されません。 |



# 例外処理で扱える変数
例外処理の中では、以下のような読み取り専用の変数の情報を参照することが可能です。これらはまとめてargsタプルに含まれます。

| 変数名 | 説明 |
|:-----------------|:------------------|
| _Error.code | Oracle Databaseのエラー番号 |
| _Error.offset | エラーのオフセット |
| _Error.message | エラーメッセージ |
| _Error.context| エラーのコンテキスト情報 |
| _Error.isrecoverable | 回復可能なエラーであるか否かのbool型。本変数を利用するためには、Oracle Server / Client共に12.1以降である必要がある。バージョン条件を満たさない場合は常にFalseが格納される |

一例としては、以下の様に例外部で上記変数を使用します。このサンプルでは、PL/SQLのユーザー定義例外機能を利用して、故意にみなさん大好き(???)なORA-600を発生させています。

```python:sample12b.py
import cx_Oracle

USERID = "admin"
PASSWORD = "FooBar"
DESTINATION = "atp1_low"
SQL = """
declare
  e600 exception;
  pragma exception_init(e600, -600);
begin
  raise e600;
end;
"""
try:
    connection = cx_Oracle.connect(USERID, PASSWORD, DESTINATION)
    cursor = connection.cursor()
    cursor.execute(SQL)
except cx_Oracle.OperationalError as ex:
    error, = ex.args
    print("エラーが発生しました。エラーコードとメッセージを管理者に連絡してください。")
    print("エラーコード : ", error.code)
    print("エラーメッセージ : ", error.message)
finally:
    cursor.close()
    connection.close()
```

```実行例
$ python sample12b.py
エラーが発生しました。エラーコードとメッセージを管理者に連絡してください。
エラーコード :  600
エラーメッセージ :  ORA-00600: 内部エラー・コード, 引数: [600], [], [], [], [], [], [], [], [], [], [], []
ORA-06512: 行6
```