46.7.2で説明したデータベースアクセスによって引き起こるエラーからの復旧は、操作の中の1つが失敗する前に、一部の操作が成功し、エラーからの復旧の後一貫性のないデータが残ってしまうという望ましくない状態を導く可能性があります。 PL/Pythonは明示的サブトランザクションにより、この問題の解法を提供します。
2つの口座の間の振替えを実装する関数を考えてみます。
CREATE FUNCTION transfer_funds() RETURNS void AS $$
try:
plpy.execute("UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'")
plpy.execute("UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'")
except plpy.SPIError, e:
result = "error transferring funds: %s" % e.args
else:
result = "funds transferred correctly"
plan = plpy.prepare("INSERT INTO operations (result) VALUES ($1)", ["text"])
plpy.execute(plan, [result])
$$ LANGUAGE plpythonu;
2番目のUPDATE文が例外を発生させる結果となった場合、この関数はエラーを記録しますが、それにもかかわらず最初のUPDATEはコミットされます。
言い換えると、資金はジョーの口座から引き落とされますが、メアリーの口座には移転しません。
こうした問題を防ぐために、plpy.execute呼び出しを明示的なサブトランザクションで囲むことができます。
plpyモジュールは、plpy.subtransaction()関数で作成される明示的なサブトランザクションを管理するための補助オブジェクトを提供します。
この関数によって作成されるオブジェクトはコンテキストマネージャインタフェースを実装します
明示的なサブトランザクションを使用して、上の関数を以下のように書き換えることができます。
CREATE FUNCTION transfer_funds2() RETURNS void AS $$
try:
with plpy.subtransaction():
plpy.execute("UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'")
plpy.execute("UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'")
except plpy.SPIError, e:
result = "error transferring funds: %s" % e.args
else:
result = "funds transferred correctly"
plan = plpy.prepare("INSERT INTO operations (result) VALUES ($1)", ["text"])
plpy.execute(plan, [result])
$$ LANGUAGE plpythonu;
try/catchの使用がまだ必要なことに注意してください。
さもないと例外がPythonスタックの最上位まで伝播され、関数全体がPostgreSQLエラーにより中断され、この結果、operationsテーブルには挿入されるはずの行が存在しないことになります。
サブトランザクションのコンテキストマネージャはエラーを捕捉しません。
これはそのスコープの内側で実行されるデータベース操作すべてが、原子的にコミットされるかロールバックされるかだけを保証します。
サブトランザクションブロックのロールバックは、データベースアクセスを元にしたエラーによって引き起こる例外だけではなく、何らかの種類の例外終了でも起こります。
明示的なサブトランザクションブロックの内側で発生した通常のPython例外も同様にサブトランザクションをロールバックさせます。
デフォルトでは、withキーワードを使用したコンテキストマネージャ構文はPython 2.6で利用可能です。
これより古いバージョンのPythonでPL/Pythonを使用する場合でも、透過性がありませんが、明示的なサブトランザクションを使用することができます。
サブトランザクションマネージャの__enter__および__exit__関数を、enterおよびexitという便利な別名を使用して、呼び出すことができます。
資金の振替えを行う関数の例は以下のように記述できます。
CREATE FUNCTION transfer_funds_old() RETURNS void AS $$
try:
subxact = plpy.subtransaction()
subxact.enter()
try:
plpy.execute("UPDATE accounts SET balance = balance - 100 WHERE account_name = 'joe'")
plpy.execute("UPDATE accounts SET balance = balance + 100 WHERE account_name = 'mary'")
except:
import sys
subxact.exit(*sys.exc_info())
raise
else:
subxact.exit(None, None, None)
except plpy.SPIError, e:
result = "error transferring funds: %s" % e.args
else:
result = "funds transferred correctly"
plan = plpy.prepare("INSERT INTO operations (result) VALUES ($1)", ["text"])
plpy.execute(plan, [result])
$$ LANGUAGE plpythonu;
コンテキストマネージャはPython 2.5で実装されましたが、このバージョンでwith構文を使用するためにはfuture文を使用する必要があります。
しかし実装上の問題のためPL/Python関数ではfuture文を使用することができません。