# 機能設計書 27-ANALYZE

## 概要

本ドキュメントは、SQLiteのANALYZEコマンドの機能設計を記述する。ANALYZEはテーブルとインデックスの統計情報を収集し、クエリオプティマイザがより効率的な実行計画を選択できるようにする。

### 本機能の処理概要

ANALYZEコマンドは、指定されたテーブルまたはデータベース全体のインデックスをスキャンし、各インデックスのキー分布に関する統計情報をsqlite_stat1/sqlite_stat4テーブルに保存する。

**業務上の目的・背景**：クエリオプティマイザは、テーブルの行数やインデックスの選択性（カーディナリティ）に基づいて最適な実行計画を決定する。統計情報がない場合、オプティマイザは推測に頼るため、非効率なプランが選択される可能性がある。ANALYZEにより正確な統計情報を提供することで、クエリパフォーマンスを向上させる。

**機能の利用シーン**：
- 大量データ投入後のクエリ最適化
- スキーマ変更後の統計更新
- 定期的なメンテナンスタスク
- パフォーマンス問題の診断

**主要な処理内容**：
1. インデックスのフルスキャンまたはサンプリング
2. 各インデックス列の値の分布計測
3. 統計情報のsqlite_statNテーブルへの保存
4. クエリプランナーへの統計情報提供

**関連システム・外部連携**：クエリプランナー（select.c, where.c）が統計情報を参照。

**権限による制御**：authorizerでANALYZE操作を制限可能

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 1 | CLIメイン | 主機能 | ANALYZEコマンド実行 |
| 8 | DBState表示 | 参照機能 | 統計情報の確認 |

## 機能種別

データベースメンテナンス / クエリ最適化

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| table_or_index | TEXT | No | テーブル名またはインデックス名 | 存在するオブジェクト |
| schema | TEXT | No | スキーマ名（schema.table形式） | 存在するスキーマ |

### 入力データソース

- SQL文によるANALYZEコマンド

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| sqlite_stat1行 | TEXT | tbl, idx, statカラム |
| sqlite_stat4行 | BLOB | サンプルデータ（STAT4有効時） |

### 出力先

- sqlite_stat1テーブル
- sqlite_stat4テーブル（SQLITE_ENABLE_STAT4時）

## 処理フロー

### 処理シーケンス

```
1. SQL解析
   └─ ANALYZE [schema[.table|index]] をパース

2. sqlite_statNテーブル準備
   └─ 存在しなければ作成
   └─ 対象テーブルのエントリを削除

3. インデックススキャン
   └─ 各インデックスについて統計収集

4. 統計計算
   └─ stat_init() で初期化
   └─ stat_push() で各行を処理
   └─ stat_get() で結果取得

5. 統計情報保存
   └─ sqlite_stat1へ挿入
   └─ sqlite_stat4へ挿入（有効時）
```

### フローチャート

```mermaid
flowchart TD
    A[ANALYZE command] --> B{対象指定?}
    B -->|なし| C[全テーブル対象]
    B -->|テーブル名| D[指定テーブルのみ]
    B -->|インデックス名| E[指定インデックスのみ]
    C --> F[sqlite_stat1準備]
    D --> F
    E --> F
    F --> G[統計テーブル作成/クリア]
    G --> H[インデックスループ]
    H --> I[stat_init]
    I --> J[行スキャンループ]
    J --> K[stat_push]
    K --> L{全行完了?}
    L -->|No| J
    L -->|Yes| M[stat_get]
    M --> N[sqlite_stat1挿入]
    N --> O{STAT4有効?}
    O -->|Yes| P[sqlite_stat4挿入]
    O -->|No| Q[次のインデックス]
    P --> Q
    Q --> R{全インデックス完了?}
    R -->|No| H
    R -->|Yes| S[完了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-27-01 | 統計テーブル自動作成 | sqlite_stat1/4が存在しなければ自動作成 | ANALYZE実行時 |
| BR-27-02 | 既存統計クリア | 対象テーブルの既存統計を削除してから再計算 | ANALYZE実行時 |
| BR-27-03 | analysis_limit | 最大スキャン行数を制限可能 | PRAGMA analysis_limit設定時 |
| BR-27-04 | サンプリング | STAT4はサンプリングによる統計収集 | SQLITE_ENABLE_STAT4時 |

### 計算ロジック

**sqlite_stat1のstat形式**:
```
"N C1 C2 ... Ck [unordered]"
```
- N: 総行数
- Ci: 最初のi列が同じ値を持つ行の平均数
- unordered: インデックスが順序なしの場合

**StatAccum構造体での統計計算**:
```c
struct StatAccum {
    tRowcnt nEst;    // 推定行数
    tRowcnt nRow;    // 訪問行数
    int nLimit;      // スキャン制限
    int nCol;        // カラム数
    ...
};
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 作成 | sqlite_stat1 | CREATE | 統計テーブル作成 |
| 削除 | sqlite_stat1 | DELETE | 対象テーブルの既存エントリ削除 |
| 挿入 | sqlite_stat1 | INSERT | 統計情報挿入 |
| 作成 | sqlite_stat4 | CREATE | サンプルテーブル作成（STAT4） |
| 挿入 | sqlite_stat4 | INSERT | サンプルデータ挿入（STAT4） |

### sqlite_stat1テーブル構造

| 操作 | カラム | 説明 | 例 |
|-----|--------|------|-----|
| INSERT | tbl | テーブル名 | 'users' |
| INSERT | idx | インデックス名（NULLで行数のみ） | 'idx_email' |
| INSERT | stat | 統計文字列 | '10000 1000 1' |

### sqlite_stat4テーブル構造

| 操作 | カラム | 説明 | 例 |
|-----|--------|------|-----|
| INSERT | tbl | テーブル名 | 'users' |
| INSERT | idx | インデックス名 | 'idx_email' |
| INSERT | neq | 等価行数リスト | '100 10 1' |
| INSERT | nlt | 未満行数リスト | '5000 500 50' |
| INSERT | ndlt | 異なり未満行数リスト | '5000 500 50' |
| INSERT | sample | サンプルキー（BLOB） | x'...' |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| SQLITE_ERROR | オブジェクト不在 | 指定テーブル/インデックスが存在しない | 正しい名前を指定 |
| SQLITE_NOMEM | メモリ不足 | 統計計算中のメモリ不足 | メモリ確保後再試行 |
| SQLITE_INTERRUPT | 割り込み | sqlite3_interrupt()呼び出し | 必要に応じて再実行 |

### リトライ仕様

特になし。エラー発生時は統計テーブルが部分的に更新される可能性あり。

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

- ANALYZEは暗黙のトランザクション内で実行
- 途中でエラーが発生した場合、それまでの変更はロールバック

## パフォーマンス要件

- PRAGMA analysis_limitで行数制限可能（デフォルト無制限）
- PRAGMA optimize (0x10) で自動的にanalysis_limitを2000に制限
- 大規模テーブルではサンプリングにより時間短縮

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

- authorizer関数でANALYZE操作を制限可能
- sqlite_statテーブルは直接操作可能だが、不正な値はクエリプランナーに悪影響

## 備考

- SQLITE_OMIT_ANALYZEでコンパイル時に無効化可能
- sqlite_stat2/stat3は廃止予定、stat4推奨
- DROP TABLE時に対応する統計エントリも自動削除

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | analyze.c | `src/analyze.c` | StatSample構造体（267-281行目） |
| 1-2 | analyze.c | `src/analyze.c` | StatAccum構造体（282-300行目） |

**読解のコツ**:
- StatSampleはサンプルデータ1件を表す（STAT4用）
- StatAccumは統計収集の累積状態を保持
- nColはインデックス列数+PK/rowid、nKeyColはインデックス列数のみ

#### Step 2: 統計テーブル操作を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | analyze.c | `src/analyze.c` | openStatTable()関数（166-251行目） |

**主要処理フロー**:
1. **173-184行目**: aTable配列で統計テーブル定義
2. **205-221行目**: テーブルがなければCREATE TABLE
3. **223-240行目**: テーブルがあればDELETE or OP_Clear

#### Step 3: SQL関数（stat_init/push/get）を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | analyze.c | `src/analyze.c` | statInit()関数 - 統計収集初期化 |
| 3-2 | analyze.c | `src/analyze.c` | statPush()関数 - 行データ追加 |
| 3-3 | analyze.c | `src/analyze.c` | statGet()関数 - 結果取得 |

**主要処理フロー**:
- stat_init(): StatAccum構造体の初期化、サンプル配列の確保
- stat_push(): 各行を処理、anDLt（異なり値数）の更新、サンプル選択
- stat_get(): 統計文字列の生成、サンプルBLOBの返却

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

```
sqlite3Analyze() [analyze.c]
    │
    ├─ openStatTable()
    │      ├─ sqlite_stat1 CREATE/CLEAR
    │      └─ sqlite_stat4 CREATE/CLEAR (STAT4有効時)
    │
    └─ analyzeOneTable()
           │
           ├─ インデックスループ
           │      │
           │      ├─ VDBEプログラム生成
           │      │      ├─ stat_init()呼び出し
           │      │      ├─ インデックススキャン
           │      │      └─ stat_push()呼び出し
           │      │
           │      ├─ stat_get()呼び出し
           │      │
           │      └─ sqlite_stat1/4 INSERT
           │
           └─ loadAnalysis() [統計情報の再読み込み]

statInit()
    └─ StatAccum初期化

statPush()
    ├─ anDLt更新
    └─ サンプル選択（STAT4）

statGet()
    ├─ 統計文字列生成
    └─ サンプルBLOB返却（STAT4）
```

### データフロー図

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

ANALYZE table ───────▶ analyzeOneTable()
                            │
                    openStatTable()
                            │
                    インデックススキャン
                            │
                            ▼
                      stat_init()
                            │
                      ┌─────┴─────┐
                      │           │
                  各行ループ      │
                      │           │
                  stat_push() ────┘
                            │
                      stat_get()
                            │
                            ▼
                    sqlite_stat1 ─────────▶ INSERT INTO sqlite_stat1
                            │
                    (STAT4有効時)
                            │
                            ▼
                    sqlite_stat4 ─────────▶ INSERT INTO sqlite_stat4
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| analyze.c | `src/analyze.c` | ソース | ANALYZE主実装 |
| where.c | `src/where.c` | ソース | 統計情報を使用するクエリプランナー |
| select.c | `src/select.c` | ソース | SELECT処理（ANALYZEと連携） |
| sqliteInt.h | `src/sqliteInt.h` | ヘッダ | Index構造体（統計情報フィールド） |
