Pager.c を読んでみる (2)
SQLiteの内部を見てみたときのメモというエントリ([前回は、Pager.c を読んでみる (1) - kuangue’s blog)。今回は、pagerPlaybackSavepoint()という関数の中身を追って見る。この関数は、pageに対するrollbackを行うときに呼び出されるとのこと。
pSavepointから、プレイバックする。もし、pSavepointがNULLの場合には、全てのマスタジャーナルファイルをプレイバックする。pSavepointがNULLとなるときは、ROLLBACK TOコマンドがSAVEPOINTに対して呼び出された時。このSAVEPOINTとは、トランザクションのsavepointのこと。
pSavepointがNULL出ないときには、ロールバク処理は三つのステージから成る。
- Pagesは、メインジャーナルの中のPagerSavepoint.iOffsetで示されるバイトからプレイバックを開始する。そして、PagerSavepoint.iHdrOffsetまでか、PagerSavepoint.iHdrOffsetが0ならば、メインジャーナルファイルの最後まで続ける。
- PagerSavepoint.iHdrOffsetが0でないならば、PagerSavepoint.iHdrOffsetの長後のジャーナルヘッダから開始して、メインジャーナルファイルの最後まで続ける。
- Pagesは、サブジャーナルファイルからプレイバックされる。PagerSavepoint.iSubRecから開始して、ジャーナルファイルの最後まで行う。
(だめだ、思想がよく分からない。理解するのにはもう少しかかるなあ)ロールバックプロセスを通して、各時刻において、pageがロールバックされ、bitvec構造体の中の関連するbitがセットされる(pDone変数のこと)。このフラグの意味合いとして、あるpageが最初にロールバックされたことを示すこともある。もし、pSavepointがNULLなら、メインジャーナルからpagesはプレイバックされる。この場合、bitvecを変更する必要はない。どちらの場合でも、プレイバックを開始する前に、Pager.dbSizeをsavepointの最初の値へリセットする。この値以上のpage-numberをもつpageに対してはプレイバックしない。もし出逢えば、スキップするだけ。
if( pSavepoint && !pagerUseWal(pPager) ){ iHdrOff = pSavepoint->iHdrOffset ? pSavepoint->iHdrOffset : szJ; pPager->journalOff = pSavepoint->iOffset; while( rc==SQLITE_OK && pPager->journalOff<iHdrOff ){ rc = pager_playback_one_page(pPager, &pPager->journalOff, pDone, 1, 1); } assert( rc!=SQLITE_DONE ); }else{ pPager->journalOff = 0; }
ここでは、pSavepointでロールバックするまでのオフセットをiHdrOffに代入し、pPager->journalOffに、pSavepointの開始オフセットを入れている。その後、pager_playback_one_page()を呼び出す。とりあえず、その中を追っかけることにしてみます。
もし、(if isMainjrnl==1)ならジャーナルファイルを、(if isMainjrnl==0)ならサブジャーナルを、ひとつのページを読み込みプレイバックする。そのページは、pOffsetから始まる。pOffsetは、ジャーナル中の次のページの先頭へ増加する。メインロールバックジャーナルは、チェックサムを利用する。ステートメントジャーナル?は利用していない。もし、ページ番号が現在のPagerのもつ番号よりも大きい場合には、処理をせずに、SQLITE_OKを返却する。(細かいことはわからないけど、Gray先生のundo/redoログの話からすると、これは、undoログのidempotent性を担保しているのではないかと思う。確か、すでにロールバックされている場合には、スキップするという話があったので。)pDoneがNULLではない場合、そのページのレコードがすでにプレイバックされていることを示している。pOffsetの示すページがすでにpレイバックされているなら、プレイバックをスキップする。そのようなことから、pDoneは、リターンされる前にかならずセットする必要がある。もし、ページレコードの読み込みが成功し、プレイバックも成功したなら、SQLITE_OKが返却される。もし、レコードの読み込み時やデータベースへの書き込み時に、I/Oエラーが発せした場合にはエラーコードを返却する。ジャーナルから読み込めたとしても、衝突していると考えられる場合には、SQLITE_DONEを返却する。次のような場合、衝突していると考える。
(わからないけど、まあいいか。)。上記の二つのシナリオは、セーブポイントロールバックでは、可能性はない。セーブポイントロールバックなら、この関数により動的にメモリが確保されないといけない。SQLITE_NOMENが返却されるのはこの場合。
今日は、ここまで。