# 機能設計書 38-セッション拡張

## 概要

本ドキュメントは、SQLiteにおけるセッション拡張（Session Extension）機能の設計仕様を定義する。データベースの変更を記録し、変更セット（changeset）/パッチセット（patchset）として出力・適用する機能である。

### 本機能の処理概要

セッション拡張は、データベースへの変更（INSERT、UPDATE、DELETE）をリアルタイムで監視・記録し、その変更内容を他のデータベースに適用可能な形式で出力する機能である。

**業務上の目的・背景**：分散システムやオフライン対応アプリケーションでは、複数のデータベース間でデータを同期する必要がある。セッション拡張により、変更内容を効率的に記録・転送・適用でき、差分同期を実現できる。

**機能の利用シーン**：
- モバイルアプリのオフライン同期（変更を記録し、オンライン時に同期）
- データベースレプリケーション
- 監査ログ（誰がいつ何を変更したかの記録）
- バージョン管理（データベースの変更履歴）
- 差分バックアップ

**主要な処理内容**：
1. sqlite3session_create()でセッションを作成
2. sqlite3session_attach()で監視対象テーブルを登録
3. pre-updateフックで変更前/変更後の値を記録
4. sqlite3session_changeset()で変更セットを出力
5. sqlite3changeset_apply()で別データベースに変更を適用
6. コンフリクト時のハンドラコールバックで解決

**関連システム・外部連携**：
- pre-updateフック：変更検出のための内部フック
- データベース同期システム：変更セットの転送先
- バックアップシステム：差分バックアップの基盤

**権限による制御**：
- 変更記録自体に特別な権限は不要
- 変更適用時は通常のテーブル操作権限が必要

## 関連画面

本機能はSQLite内部機能のため、直接的な画面関連はない。

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | - |

## 機能種別

データ同期 / 変更追跡 / レプリケーション

## 入力仕様

### 入力パラメータ（セッション作成）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| db | sqlite3* | Yes | データベース接続ハンドル | NULLでないこと |
| zDb | const char* | Yes | データベース名（"main"等） | 有効なDB名 |
| ppSession | sqlite3_session** | Yes | セッションハンドル出力先 | NULLでないこと |

### 入力パラメータ（変更適用）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| db | sqlite3* | Yes | 適用先データベース | NULLでないこと |
| nChangeset | int | Yes | 変更セットのバイト数 | 正の整数 |
| pChangeset | void* | Yes | 変更セットデータ | NULLでないこと |
| xFilter | callback | No | テーブルフィルター | - |
| xConflict | callback | Yes | コンフリクトハンドラ | NULLでないこと |
| pCtx | void* | No | コールバック用ユーザーデータ | - |

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| changeset | BLOB | 変更セット（全フィールド含む） |
| patchset | BLOB | パッチセット（変更フィールドのみ） |
| SQLITE_OK | int | 成功 |
| SQLITE_CHANGESET_* | int | コンフリクト解決コード |

### 変更セットフォーマット

```
テーブルヘッダ:
  0x54 ('T') | カラム数 | PK配列 | テーブル名(NULL終端)

各変更レコード:
  操作種別(INSERT=0x12/UPDATE=0x17/DELETE=0x09) |
  indirect-flagバイト |
  old.*レコード(DELETE/UPDATEのみ) |
  new.*レコード(INSERT/UPDATEのみ)
```

## 処理フロー

### 処理シーケンス

```
1. セッション作成フェーズ
   ├─ sqlite3session_create()でセッション作成
   ├─ sqlite3session_attach()でテーブル登録（NULLで全テーブル）
   └─ pre-updateフックの設定

2. 変更記録フェーズ
   ├─ INSERT/UPDATE/DELETE実行時にpre-updateフック発火
   ├─ 変更前の値（old.*）と変更後の値（new.*）を取得
   ├─ SessionChangeハッシュテーブルに記録
   └─ 同一PKへの複数変更は統合

3. 変更セット出力フェーズ
   ├─ sqlite3session_changeset()で全変更を出力
   ├─ バイナリレコード形式でシリアライズ
   └─ ストリーミング出力（sqlite3session_changeset_strm）も可能

4. 変更適用フェーズ
   ├─ sqlite3changeset_apply()で変更を適用
   ├─ 各レコードに対してINSERT/UPDATE/DELETE実行
   ├─ コンフリクト発生時にxConflictコールバック
   └─ OMIT/ABORT/REPLACE等で解決
```

### フローチャート

```mermaid
flowchart TD
    A[sqlite3session_create] --> B[sqlite3session_attach]
    B --> C[pre-updateフック設定]

    D[INSERT/UPDATE/DELETE] --> E[pre-updateフック発火]
    E --> F[old.*/new.*値取得]
    F --> G[SessionChangeハッシュに記録]

    H[sqlite3session_changeset] --> I[ハッシュテーブル走査]
    I --> J[バイナリ形式でシリアライズ]
    J --> K[changeset出力]

    L[sqlite3changeset_apply] --> M[changesetパース]
    M --> N[各レコードを適用]
    N --> O{コンフリクト?}
    O -->|Yes| P[xConflictコールバック]
    O -->|No| Q[次のレコード]
    P --> R{解決方法}
    R -->|OMIT| Q
    R -->|REPLACE| S[強制上書き]
    R -->|ABORT| T[適用中止]
    S --> Q
    Q --> U{全レコード処理完了?}
    U -->|No| N
    U -->|Yes| V[完了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-38-1 | PK必須 | 変更追跡にはプライマリキーが必要 | 常時 |
| BR-38-2 | 変更統合 | 同一PKへの複数変更は最終結果に統合 | セッション内 |
| BR-38-3 | コンフリクト解決 | 適用時のコンフリクトはコールバックで解決 | 適用時 |
| BR-38-4 | indirect変更 | トリガーによる間接変更はindirectフラグで識別 | トリガー発火時 |

### 計算ロジック

**変更統合ロジック**：
- INSERT後UPDATE: INSERTの新値をUPDATE後の値に更新
- INSERT後DELETE: 両方を削除（無変更として扱う）
- UPDATE後UPDATE: old値を維持、new値を最新に更新
- UPDATE後DELETE: DELETEのみ（old値を維持）
- DELETE後INSERT: UPDATEとして扱う

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 記録 | なし | なし | メモリ内記録のみ |
| 適用 | 対象テーブル | INSERT/UPDATE/DELETE | 変更セットの内容を適用 |

### テーブル別操作詳細

変更適用時:

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | 全カラム | new.*値 | 新規行挿入 |
| UPDATE | 変更カラム | new.*値 | WHERE PK = old.PK |
| DELETE | - | - | WHERE PK = old.PK |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| SQLITE_CHANGESET_DATA | データ不整合 | UPDATEのold値と現在値が不一致 | OMIT/ABORT/REPLACE |
| SQLITE_CHANGESET_NOTFOUND | 行不在 | DELETE/UPDATE対象行が存在しない | OMIT/ABORT |
| SQLITE_CHANGESET_CONFLICT | 一意制約違反 | INSERT時に既存行と衝突 | OMIT/ABORT/REPLACE |
| SQLITE_CHANGESET_CONSTRAINT | 制約違反 | FK制約等の違反 | OMIT/ABORT |
| SQLITE_CHANGESET_FOREIGN_KEY | FK違反 | 外部キー制約違反（適用完了後チェック） | - |

### コンフリクト解決

| 解決方法 | コード | 動作 |
|---------|--------|------|
| SQLITE_CHANGESET_OMIT | 0 | 該当変更をスキップ |
| SQLITE_CHANGESET_REPLACE | 1 | 強制的に上書き |
| SQLITE_CHANGESET_ABORT | 2 | 適用を中止 |

## トランザクション仕様

- セッションはトランザクション境界を跨いで記録可能
- 変更適用（sqlite3changeset_apply）は内部でトランザクションを使用
- ロールバック時、記録済み変更は取り消されない（明示的にリセットが必要）

## パフォーマンス要件

- 変更記録はハッシュテーブルを使用し、O(1)でアクセス
- 変更セットサイズはsqlite3session_changeset_size()で事前取得可能
- ストリーミングAPI（_strm）でメモリ使用量を制限可能
- bEnableSizeでサイズ追跡を有効化（パフォーマンスへの影響あり）

## セキュリティ考慮事項

- 変更セットには機密データが含まれる可能性がある
- 転送時は暗号化を検討
- 変更適用時のコンフリクトハンドラで適切な検証を実施

## 備考

- SQLITE_ENABLE_SESSION および SQLITE_ENABLE_PREUPDATE_HOOK でコンパイル時に有効化
- sqlite3changeset_invert()で変更を反転（元に戻す）
- sqlite3changeset_concat()で複数の変更セットを連結
- sqlite3changegroup_*()で変更セットをグループ化・マージ
- rebase blob機能でコンフリクト解決後の再適用をサポート

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

セッション拡張の中核となるデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | sqlite3session.c | `ext/session/sqlite3session.c` | 44-61行目のsqlite3_session構造体 |
| 1-2 | sqlite3session.c | `ext/session/sqlite3session.c` | 98-112行目のsqlite3_changeset_iter構造体 |
| 1-3 | sqlite3session.c | `ext/session/sqlite3session.c` | 139-154行目のSessionTable構造体 |

**読解のコツ**: sqlite3_sessionがセッション全体を管理、SessionTableがテーブル単位の変更を管理、SessionChangeが各行の変更を管理する階層構造。

#### Step 2: レコードフォーマットを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | sqlite3session.c | `ext/session/sqlite3session.c` | 156-277行目のRECORD FORMAT/CHANGESETフォーマットコメント |

**主要処理フロー**:
- フィールド型バイト（0x00〜0x05）
- varint/big-endian整数エンコーディング
- テーブルヘッダとレコード構造

#### Step 3: 変更記録処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | sqlite3session.c | `ext/session/sqlite3session.c` | sessionPreupdateOneを検索 |

**主要処理フロー**:
- pre-updateフックからの呼び出し
- old.*/new.*値の取得
- SessionChangeハッシュテーブルへの追加/更新

#### Step 4: 変更セット出力を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | sqlite3session.c | `ext/session/sqlite3session.c` | sqlite3session_changeset()関数 |

**主要処理フロー**:
- 各テーブルの変更をイテレート
- バイナリ形式へのシリアライズ
- SessionBuffer構造体でのバッファ管理

#### Step 5: 変更適用処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | sqlite3session.c | `ext/session/sqlite3session.c` | sqlite3changeset_apply_v2()関数 |

**主要処理フロー**:
- changesetイテレーターでパース
- INSERT/UPDATE/DELETEの実行
- コンフリクトハンドラの呼び出し

### プログラム呼び出し階層図

```
sqlite3session_create()
    │
    └─ sqlite3_session構造体を作成

sqlite3session_attach()
    │
    └─ SessionTableを作成してリストに追加

[DML実行時]
pre-updateフック
    │
    └─ sessionPreupdateOne()
           │
           ├─ xOld()/xNew()で値取得
           └─ SessionChangeハッシュに記録

sqlite3session_changeset()
    │
    └─ sessionGenerateChangeset()
           │
           ├─ 各SessionTableをイテレート
           └─ sessionAppendTableHdr() + sessionAppendRecord()

sqlite3changeset_apply_v2()
    │
    ├─ sqlite3changeset_start_v2()  イテレーター作成
    │
    └─ sessionChangesetApply()
           │
           ├─ sqlite3changeset_next()  次のレコード
           ├─ sessionApplyOneOp()      INSERT/UPDATE/DELETE実行
           └─ xConflict()              コンフリクト時コールバック
```

### データフロー図

```
[入力]                      [処理]                           [出力]

INSERT/UPDATE/DELETE ─────▶ pre-updateフック ──────────────▶ SessionChange
DML操作                     sessionPreupdateOne()            ハッシュテーブル

SessionChange ─────────────▶ sessionGenerateChangeset() ───▶ changeset
ハッシュテーブル             シリアライズ                     バイナリBLOB

changeset ─────────────────▶ sqlite3changeset_apply_v2() ─▶ 更新済みDB
バイナリBLOB                 │                               テーブル
                           ├─ パース
                           ├─ DML実行
                           └─ xConflict()

changeset ─────────────────▶ sqlite3changeset_invert() ───▶ 反転changeset
                            反転処理                         元に戻す用
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| sqlite3session.c | `ext/session/sqlite3session.c` | ソース | セッション拡張メイン実装 |
| sqlite3session.h | `ext/session/sqlite3session.h` | ヘッダー | 公開API定義 |
| changeset.c | `ext/session/changeset.c` | ソース | changesetツール |
| changesetfuzz.c | `ext/session/changesetfuzz.c` | ソース | ファジングテスト |
| test_session.c | `ext/session/test_session.c` | ソース | テスト用実装 |
