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を返却する。次のような場合、衝突していると考える。

  • レコードのページ番号が不正(0 or PAGER_MJ_PGNO),or
  • メインジャーナルからレコードがロールバックされていて、レコードの内容がチェックサムフィールと一致しない。

(わからないけど、まあいいか。)。上記の二つのシナリオは、セーブポイントロールバックでは、可能性はない。セーブポイントロールバックなら、この関数により動的にメモリが確保されないといけない。SQLITE_NOMENが返却されるのはこの場合。

今日は、ここまで。