# 機能設計書 24-BLOBストリーミング

## 概要

本ドキュメントは、SQLiteのBLOBストリーミング（Incremental BLOB I/O）機能の設計を記述する。この機能は大容量BLOBデータの部分読み書きを可能にし、メモリ効率の良いBLOB操作を実現する。

### 本機能の処理概要

BLOBストリーミングは、BLOB列のデータ全体をメモリにロードすることなく、指定したオフセットから指定したバイト数だけ読み書きできるインクリメンタルI/O機能を提供する。

**業務上の目的・背景**：画像、動画、大容量ドキュメントなどのバイナリデータをデータベースに保存する場合、通常のSQL文（SELECT/UPDATE）では全データをメモリに展開する必要がある。BLOBストリーミングを使用することで、数GBのBLOBデータでも少量のメモリで部分的にアクセスでき、ストリーミング配信やプログレッシブダウンロードなどのユースケースに対応できる。

**機能の利用シーン**：
- 大容量ファイルのストリーミング読み込み
- 部分更新（ファイルの一部のみ変更）
- メモリ制約のある環境でのBLOB操作
- 進行中のアップロード/ダウンロードの再開

**主要な処理内容**：
1. BLOBハンドルのオープン（テーブル/行/列の指定）
2. 任意オフセットからのBLOB読み取り
3. 任意オフセットへのBLOB書き込み（サイズ変更不可）
4. BLOBサイズの取得
5. 同一列の別行へのハンドル再オープン

**関連システム・外部連携**：VDBEを通じてB-Treeカーソルを直接操作し、ページ単位でBLOBデータにアクセスする。

**権限による制御**：書き込み時はインデックス付き列や外部キー列への書き込みが禁止される

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 画面から直接利用されないAPI機能 |

## 機能種別

データアクセス / ストリーミングI/O

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| db | sqlite3* | Yes | データベース接続 | オープン済み |
| zDb | const char* | No | データベース名 | "main"等、NULLで"main" |
| zTable | const char* | Yes | テーブル名 | 存在するテーブル |
| zColumn | const char* | Yes | カラム名 | BLOB/TEXT型 |
| iRow | sqlite3_int64 | Yes | 行ID（rowid） | 存在する行 |
| wrFlag | int | Yes | 書き込みフラグ | 0=読み取り、非0=読み書き |

### 入力データソース

- アプリケーションからのsqlite3_blob_open()呼び出し

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| ppBlob | sqlite3_blob** | BLOBハンドル |
| rc | int | 操作結果 |
| bytes | int | 読み書きバイト数（blob_bytes） |

### 出力先

- アプリケーションへのBLOBハンドル返却
- 書き込み時はデータベースファイル

## 処理フロー

### 処理シーケンス

```
1. BLOBオープン (sqlite3_blob_open)
   └─ テーブル・列検証
   └─ VDBE作成（内部的にSELECTを実行）
   └─ B-Treeカーソル取得

2. BLOB読み取り (sqlite3_blob_read)
   └─ オフセット・サイズ検証
   └─ B-Treeカーソルからデータ取得

3. BLOB書き込み (sqlite3_blob_write)
   └─ オフセット・サイズ検証
   └─ インデックス/外部キー検証（オープン時）
   └─ B-Treeカーソルへデータ書き込み

4. BLOB再オープン (sqlite3_blob_reopen)
   └─ 同一カラムの別行へシーク

5. BLOBクローズ (sqlite3_blob_close)
   └─ VDBEファイナライズ
   └─ リソース解放
```

### フローチャート

```mermaid
flowchart TD
    A[sqlite3_blob_open] --> B{テーブル存在?}
    B -->|No| C[エラー: テーブルなし]
    B -->|Yes| D{カラム存在?}
    D -->|No| E[エラー: カラムなし]
    D -->|Yes| F{行存在?}
    F -->|No| G[エラー: 行なし]
    F -->|Yes| H{BLOB/TEXT型?}
    H -->|No| I[エラー: 型不正]
    H -->|Yes| J{wrFlag && インデックス付き?}
    J -->|Yes| K[エラー: 書き込み不可]
    J -->|No| L[BLOBハンドル返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-24-01 | サイズ変更禁止 | blob_writeでBLOBサイズは変更不可 | 書き込み時 |
| BR-24-02 | インデックス列書き込み禁止 | インデックスに含まれる列は書き込み不可 | wrFlag=1時 |
| BR-24-03 | 外部キー列書き込み禁止 | 外部キーの一部である列は書き込み不可 | 外部キー有効時 |
| BR-24-04 | 仮想テーブル非対応 | 仮想テーブルのBLOBは操作不可 | 常時 |
| BR-24-05 | WITHOUT ROWID非対応 | WITHOUT ROWIDテーブルは操作不可 | 常時 |
| BR-24-06 | VIEW非対応 | VIEWのBLOBは操作不可 | 常時 |
| BR-24-07 | 生成列非対応 | 生成列（GENERATED）は操作不可 | 常時 |

### 計算ロジック

**BLOBオフセット計算**:
```c
// Incrblob構造体のiOffsetフィールド
// BLOBデータはレコード内のシリアルタイプに続いて格納される
p->iOffset = pC->aType[p->iCol + pC->nField];
p->nByte = sqlite3VdbeSerialTypeLen(type);
```

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

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

| 操作 | 対象 | 操作種別 | 概要 |
|-----|-------------|---------|------|
| blob_open | DBファイル | READ | カーソルオープン |
| blob_read | DBファイル | READ | BLOBデータ読み取り |
| blob_write | DBファイル | WRITE | BLOBデータ書き込み |

### 内部VDBE操作

| 操作 | VDbeオペコード | 説明 |
|-----|---------------|------|
| テーブルロック | OP_TableLock | 排他または共有ロック |
| カーソルオープン | OP_OpenRead/Write | B-Treeカーソル取得 |
| 行シーク | OP_NotExists | rowid指定でシーク |
| 結果行 | OP_ResultRow | カーソル位置を記録 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| SQLITE_ERROR | 一般エラー | テーブル/カラム/行が存在しない | 正しいパラメータで再試行 |
| SQLITE_ERROR | 型エラー | NULL/INTEGER/REAL型の列を開いた | BLOB/TEXT列のみ使用可 |
| SQLITE_ERROR | 仮想テーブル | 仮想テーブルを開こうとした | 通常テーブルを使用 |
| SQLITE_ERROR | インデックス列 | インデックス付き列に書き込み | 読み取り専用でオープン |
| SQLITE_ABORT | トランザクション中止 | 関連トランザクションがロールバック | 再オープン |
| SQLITE_MISUSE | API誤用 | 不正なポインタ渡し | 正しいAPIを使用 |

### リトライ仕様

SQLITE_ABORT後はハンドルが無効になるため、sqlite3_blob_close()後に再度sqlite3_blob_open()が必要。

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

- BLOBハンドルは暗黙のトランザクション内で動作
- blob_writeは即座にデータベースに書き込むが、明示的コミットまで他トランザクションからは見えない
- 行の削除や変更があった場合、以降のblob操作はSQLITE_ABORTを返す

## パフォーマンス要件

- blob_read/blob_writeはO(1)アクセス（B-Treeシーク済み）
- blob_reopenは同一テーブルの別行への高速シーク
- 大容量BLOBの部分アクセスは全体ロードより大幅に高速

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

- wrFlag=1でオープンした場合、インデックス整合性チェックはオープン時のみ
- 書き込み後のデータは即座にディスクに反映される可能性あり

## 備考

- SQLITE_OMIT_INCRBLOBでコンパイル時に無効化可能
- blob_reopenは同一テーブル・同一列の別行へのシークに最適化

---

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

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

### 推奨読解順序

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

まず、BLOBハンドルの内部構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | vdbeblob.c | `src/vdbeblob.c` | Incrblob構造体（25-34行目） |

**読解のコツ**:
- nByte: オープンしたBLOBのサイズ
- iOffset: レコード内のBLOBデータ開始位置
- pCsr: B-Treeカーソルへのポインタ
- pStmt: 内部で使用するVDBE statement

#### Step 2: BLOBオープンを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | vdbeblob.c | `src/vdbeblob.c` | sqlite3_blob_open()関数（121-300行目以降） |

**主要処理フロー**:
1. **121-149行目**: パラメータ検証、wrFlagの正規化
2. **162-194行目**: テーブル検索とテーブル種別チェック（仮想テーブル、WITHOUT ROWID、VIEW、生成列）
3. **199-206行目**: カラム検索
4. **211-248行目**: 書き込み時のインデックス・外部キーチェック
5. **250-278行目**: VDBEプログラム作成

#### Step 3: VDBEプログラムを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | vdbeblob.c | `src/vdbeblob.c` | openBlob配列（270-278行目） |

**VDBEプログラム構造**:
```c
static const VdbeOpList openBlob[] = {
    {OP_TableLock,      0, 0, 0},  /* 0: ロック取得 */
    {OP_OpenRead,       0, 0, 0},  /* 1: カーソルオープン */
    {OP_NotExists,      0, 5, 1},  /* 2: rowid=r[1]でシーク */
    {OP_Column,         0, 0, 1},  /* 3: カラム取得 */
    {OP_ResultRow,      1, 0, 0},  /* 4: 結果行 */
    {OP_Halt,           0, 0, 0},  /* 5: 停止 */
};
```

#### Step 4: 行シークを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | vdbeblob.c | `src/vdbeblob.c` | blobSeekToRow()関数（54-116行目） |

**主要処理フロー**:
- **62行目**: r[1]レジスタにrowidを設定
- **68-74行目**: VDBEプログラムを再実行（pc=4へジャンプ）
- **75-96行目**: 結果検証とBLOB情報（offset, size）の取得

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

```
sqlite3_blob_open()
    │
    ├─ sqlite3LocateTable()
    │      └─ テーブル検索
    │
    ├─ sqlite3ColumnIndex()
    │      └─ カラム検索
    │
    ├─ インデックス/外部キーチェック
    │
    ├─ sqlite3VdbeCreate()
    │      └─ VDBE作成
    │
    ├─ sqlite3VdbeAddOpList(openBlob)
    │      └─ VDBEプログラム構築
    │
    └─ blobSeekToRow()
           │
           ├─ sqlite3VdbeMemSetInt64() [r[1]=rowid]
           │
           └─ sqlite3VdbeExec()
                  └─ OP_NotExistsでシーク

sqlite3_blob_read()
    │
    └─ sqlite3BtreePayloadChecked()
           └─ B-Treeからデータ読み取り

sqlite3_blob_write()
    │
    └─ sqlite3BtreePutData()
           └─ B-Treeへデータ書き込み

sqlite3_blob_reopen()
    │
    └─ blobSeekToRow()
           └─ 新しいrowidへシーク
```

### データフロー図

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

db, table, column ──▶ sqlite3_blob_open()
rowid, wrFlag                │
                    テーブル/カラム検証
                             │
                    VDBEプログラム作成
                             │
                    blobSeekToRow()
                             │
                    B-Treeカーソル取得
                             │
                             ▼
                    sqlite3_blob* ──────────▶ アプリケーション

BLOBハンドル ────────▶ sqlite3_blob_read()
offset, n, buffer            │
                    オフセット検証
                             │
                    sqlite3BtreePayloadChecked()
                             │
                             ▼
                    バッファへ読み取り ──────▶ アプリケーション

BLOBハンドル ────────▶ sqlite3_blob_write()
offset, n, buffer            │
                    オフセット検証
                             │
                    sqlite3BtreePutData()
                             │
                             ▼
                    データベース更新
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| vdbeblob.c | `src/vdbeblob.c` | ソース | BLOBストリーミング主実装 |
| vdbeInt.h | `src/vdbeInt.h` | ヘッダ | VDBE内部構造体定義 |
| btree.c | `src/btree.c` | ソース | B-Tree操作（ペイロードアクセス） |
| sqlite3.h | `src/sqlite3.h` | ヘッダ | 公開API（sqlite3_blob_*） |
