トランザクションIDに関する関数の追加

# はじめに
にゃーん
この記事は、[PostgreSQL 10全部ぬこ Advent Calendar 2017](https://qiita.com/advent-calendar/2017/pg10-nuko-only) の18日目のエントリです。
うー、あと一週間。なんとか完走できるといいなー。

今日はPostgreSQL 10で追加されたトランザクションIDに関するSQL関数について軽く。
PostgresQL 10では以下の2関数が追加された。

|関数名|引数|返却型|概要|
|:--|:--|:--|:--|
|txid_current_if_assigned()|なし|bigint|トランザクションIDが既に割り当てられている場合は、txid_current()と同様にトランザクションIDを返却する。トランザクションIDが割り当てられていない状態だと、トランザクションIDをアサインする代わりにnullを返却する|
|txid_status()|xid bigint|txid_status|xidで与えたトランザクションIDの状態(commited, abort, in progress, null)を返却する。|

PostgreSQLでは慣習的にトランザクションのことを"tx"や"x"と表記する文化らしく、このためトランザクションIDも"xid"と表記することが多い。また、それがSQL関数の名前にも使われている。

#txid_current_if_assigned()

psqlで検証する場合、以下のような準備をしておくと見やすい。

```postgres-console
xid=# \pset null (null)
Null display is "(null)".
xid=# 
```
こうしておくと、nullが返却されたときに```(null)```と表示されるので見やすくなる。

まず、以前から存在していた```txid_current()```を実行する。
この関数は新たにトランザクションIDを割り当て、そのトランザクションIDを返却する。

```postgres-console
xid=# SELECT txid_current();
 txid_current 
--------------
      1287942
(1 row)
```

同じように```txid_current_if_assigned()```を実行した場合、発行時点ではトランザクションIDは割り当てられていないので、nullが返却される。

```
xid=# SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                   (null)
(1 row)
```

ここまではpsqlのAUTOCOMMITモード上での動作となる。
今度は、明示的にトランザクションを開始し、そのトランザクション内で、```txid_current()```と、```txid_current_if_assigned()```を実行してみる。

```postgres-console
xid=# BEGIN;
BEGIN
xid=# SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                         
(1 row)

xid=# SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                         
(1 row)

xid=# SELECT txid_current();
 txid_current 
--------------
      1287950
(1 row)

xid=# SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                  1287950
(1 row)

xid=# ROLLBACK;
ROLLBACK
xid=# 
```
トランザクション開始直後に```txid_current_if_assigned()```を実行してもnullである。
明示的に```txid_current()```を実行した後だと、```txid_current_if_assigned()```も同じトランザクションIDを返却する。

トランザクションIDは、BEGINでトランザクションを開始した後には払い出されない。INSERT/UPDATE/DELETE等の更新文が発行される契機でトランザクションIDが発行される。

```postgres-console
xid=# BEGIN;
BEGIN
xid=# SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                   (null)
(1 row)

xid=# INSERT INTO test VALUES (1, 'aaa');
INSERT 0 1
xid=# SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                  1287952
(1 row)
```
で、この関数、どういうときに使うのか。実はあんまり自分でもよく分かっていない。
でも、psql上で使うなら、 \if, \then, \els \endif などと組合せて、トランザクションIDの有無で、後続のSQL発行を分岐させたりするのに役に立つのかも。

# txid_status()
## 実行例

トランザクションを開始し、その直後に```txid_current_if_assigned```を実行してもnullが返却されるのは、上で説明したとおり。
では、このnullのトランザクションIDを```txid_status()```に与えると・・・nullが返却される。ふむ。

```postgres-console
xid=# SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                   (null)
(1 row)

xid=# SELECT txid_status(null);
 txid_status 
-------------
 (null)
(1 row)
```

同じトランザクション内で、INSERT文を実行する。これでトランザクションIDは払い出されるはず。

```postgres-console
xid=# INSERT INTO test VALUES (1, 'aaa');
INSERT 0 1
xid=# SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                  1287956
(1 row)

xid=# SELECT txid_status(1287956);
 txid_status 
-------------
 in progress
(1 row)
```
払い出されたトランザクションID(1287956)を```txid_status()```に与えると、```in progress```と表示される。
さらにこの状態でCOMMITして、その後に同じトランザクションIDの状態を見ると

```postgres-console
xid=# COMMIT;
COMMIT
xid=# SELECT txid_status(1287956);
 txid_status 
-------------
 committed
(1 row)
```

このように```committed```と表示される。

トランザクションがロールバックした場合には、

```postgres-console
xid=# BEGIN;
BEGIN
xid=# DELETE FROM test;
DELETE 1
xid=# SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                  1287957
(1 row)

xid=# SELECT txid_status(1287957);
 txid_status 
-------------
 in progress
(1 row)

xid=# ROLLBACK;
ROLLBACK
xid=# SELECT txid_status(1287957);
 txid_status 
-------------
 aborted
(1 row)
```

このように```aborted```と表示される。

## この関数の用途は？

マニュアルをちら見した感じだと、二層コミットの状態確認なんかに使えるようにも思える。

## 良くわからない挙動・・・
で、これを動かしていて、「ん？」という動作があった。

```postgres-console
xid=# BEGIN;
BEGIN
xid=# DELETE FROM test;
DELETE 1
xid=# SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                  1287960
(1 row)
```
で、今のトランザクションIDを```txid_status()```に与えてみる。

```postgres-console
xid=# SELECT txid_status(1287960);
 txid_status 
-------------
 in progress
(1 row)
```

うん。ここまではわかる。で、今のトランザクションIDから1つ進めた数を```txid_status()```に与えてみる。

```postgres-console
xid=# SELECT txid_status(1287961);
 txid_status 
-------------
 in progress
(1 row)
```

えええ、これもin progressになるのか。
さらに1つ先の数を指定すると、

```postgres-console
xid=# SELECT txid_status(1287962);
ERROR:  transaction ID 1287962 is in the future
```
今度はエラーになってしまった。
エラーになったので仕方がない。ROLLBACKしよう。

```postgres-console
xid=# ROLLBACK;
ROLLBACK
```

で、ROLLBACKしたあと、1287960, 1287961のトランザクションIDの状態を見ると、

```postgres-console
xid=# SELECT txid_status(1287960);
 txid_status 
-------------
 aborted
(1 row)

xid=# SELECT txid_status(1287961);
 txid_status 
-------------
 in progress
(1 row)

xid=# 
```
1287960は```aborted```に、1287961は```in progress```になる。これが正しい挙動なのかどうか良くわからない・・・。

### おまけ
関数```xid_status()```は、```backend/utils/adt/txid.c```の792行目(PostgreSQL 10.1の場合)あたりに実装コードがある。
で、```ERROR:  transaction ID 1287962 is in the future```のエラーメッセージは、その関数内で最初のほうに実行している```TransactionIdInRecentPast()```の中で出力されているっぽい。
現状の作りはそうなんだー、なんだけどせめてPostgreSQL文書の```xid_status()```関数の説明のところには、ERRORになるケースを書いてもいいんじゃないかなー。

とふと思った。

# 参考：該当するリリースノート
本エントリに関連するPostgreSQL 10リリースノートの記載です。

E.2.3.1.5. General Performance

> * Add function txid_current_if_assigned() to return the current transaction ID or NULL if no transaction ID has been assigned (Craig Ringer)
> * Add function txid_status() to check if a transaction was committed (Craig Ringer)