# 機能設計書 35-SQLスクリプティング

## 概要

本ドキュメントは、Apache SparkにおけるSQLスクリプティング機能に関する機能設計書である。IF/ELSE、WHILE、REPEAT、FOR、CASE等の制御構造を用いたSQLスクリプトの実行機能を提供し、手続き型SQLプログラミングをSpark SQL上で実現する。

### 本機能の処理概要

**業務上の目的・背景**：従来のSpark SQLは宣言型クエリの実行に特化しており、条件分岐やループを伴う手続き型の処理を実現するにはプログラミング言語（Scala/Python）への切り替えが必要であった。SQLスクリプティングにより、SQL標準に準拠した制御構造をSQLのみで記述でき、データベースエンジニアが慣れ親しんだストアドプロシージャスタイルの処理をSpark上で実行可能となる。

**機能の利用シーン**：条件に基づいたデータ変換バッチ処理、ループによる段階的なデータ処理、例外ハンドリング付きのETLスクリプト、カーソルを使用した行単位処理。

**主要な処理内容**：
1. CompoundBody解析：SQLパーサーがSQLスクリプトをCompoundBody論理計画に変換
2. 実行計画構築：SqlScriptingInterpreterがCompoundBodyをCompoundBodyExec実行ノードツリーに変換
3. スクリプト実行：SqlScriptingExecutionが実行ノードを順次評価し、結果DataFrameを返却
4. 制御構造サポート：IF/ELSE、WHILE、REPEAT、FOR、LOOP、CASE（SearchedCase/SimpleCase）
5. 例外ハンドリング：DECLARE HANDLERによるCONTINUE/EXITハンドラーの登録と実行
6. ローカル変数管理：DECLARE文によるスクリプトスコープの変数宣言と管理
7. カーソルサポート：DECLARE CURSOR/OPEN/FETCH/CLOSEによるカーソル操作

**関連システム・外部連携**：Catalyst SQLパーサー（ANTLR4ベース）、SparkSession

**権限による制御**：SQLスクリプト内の個々のSQL文はSparkの標準認可メカニズムに従う。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 本機能に直接関連するWeb UI画面はない |

## 機能種別

計算処理 / データ変換 / バッチ処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| sqlScript | CompoundBody | Yes | パース済みSQLスクリプトの論理計画 | 有効なCompoundBody |
| session | SparkSession | Yes | 実行対象のSparkSession | アクティブなセッション |
| args | Map[String, Expression] | No | パラメータ名→SQL式のマップ | 有効なExpression |

### 入力データソース

- SQL文テキスト（SQLパーサーによりCompoundBodyに変換された後に入力）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| DataFrame | Option[DataFrame] | スクリプト内の結果ステートメント（SELECT文等）の結果 |
| LogicalPlan | LogicalPlan | executeSqlScript()の場合、最終結果のLocalRelation |

### 出力先

- Spark DataFrame（対話型実行時）
- LocalRelation（executeSqlScript経由）

## 処理フロー

### 処理シーケンス

```
1. SQLスクリプトパース
   └─ Catalyst SQLパーサーがCompoundBodyを生成
2. 実行計画構築
   └─ SqlScriptingInterpreter.buildExecutionPlan()がCompoundBodyExecを構築
3. コンテキスト初期化
   └─ SqlScriptingExecutionContextのフレームスタックにスクリプトフレームを追加
4. ステートメント逐次実行
   └─ getNextResult()がフレームスタックから次のステートメントを取得して実行
5. 制御構造評価
   └─ evaluateBooleanCondition()でIF/WHILE等の条件を評価
6. 例外ハンドリング
   └─ SparkThrowable発生時にcontext.findHandler()でハンドラーを検索
7. 結果返却
   └─ SELECT文等の結果DataFrameを呼び出し元に返却
```

### フローチャート

```mermaid
flowchart TD
    A[SQLスクリプト] --> B[Catalyst SQLパーサー]
    B --> C[CompoundBody]
    C --> D[SqlScriptingInterpreter.buildExecutionPlan]
    D --> E[CompoundBodyExec]
    E --> F[SqlScriptingExecution]
    F --> G{getNextStatement}
    G -->|SingleStatementExec| H[buildDataFrame]
    H --> I{CommandResult?}
    I -->|Yes| G
    I -->|No| J[結果DataFrame返却]
    G -->|NonLeafStatementExec| K[制御構造評価]
    K --> G
    G -->|None| L[スクリプト終了]
    H -->|例外発生| M{ハンドラー検索}
    M -->|Found| N[ハンドラーフレーム追加]
    N --> G
    M -->|Not Found| O{完了条件?}
    O -->|Yes| G
    O -->|No| P[例外再送出]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | ステートメント順次実行 | スクリプト内のステートメントは定義順に逐次実行される | 全ステートメント |
| BR-02 | LEAVE/ITERATE | LEAVE文はラベル付きブロックからの脱出、ITERATE文はループの次の反復への遷移 | ループ/ブロック内 |
| BR-03 | EXIT_HANDLER | EXITハンドラー実行後、ハンドラーのスコープラベルまでLEAVEを注入 | 例外発生→EXITハンドラー実行完了時 |
| BR-04 | CONTINUE_HANDLER | CONTINUEハンドラー実行後、条件文が評価中なら中断してスキップ | 例外発生→CONTINUEハンドラー実行完了時 |
| BR-05 | SQLSTATE '02'処理 | 完了条件（SQLSTATE class '02' - no data）はハンドラー未定義でも例外として再送出しない | FETCH等でデータなし時 |
| BR-06 | Boolean条件評価 | 条件式はBooleanType単一カラム単一行を返す必要がある。NULL値はfalseとして評価 | IF/WHILE/REPEAT/CASE条件 |

### 計算ロジック

- **フレームスタック管理**: context.frames（ArrayBuffer）がネストされた実行コンテキストを管理。最後のフレームからステートメントを取得
- **条件評価**: evaluateBooleanCondition()がDataFrameを実行し、結果のBoolean値を取得。NULL → false変換
- **例外ハンドリング**: handleException()がcontext.findHandler()でconditionName/sqlStateに一致するハンドラーを検索

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| スクリプト内SQL | 任意 | 任意 | スクリプト内のSQL文に依存 |

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

スクリプト内の個々のSQL文のデータベース操作に依存する。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| INTERNAL_ERROR | SparkException | Boolean条件が複数行を返す | 条件式を修正 |
| UNHANDLED_EXCEPTION | SparkThrowable | ハンドラー未定義の非完了条件例外 | HANDLERを宣言するかプログラムで処理 |
| DUPLICATE_HANDLER | SqlScriptingError | 同一条件に対する重複ハンドラー定義 | 重複を解消 |

### リトライ仕様

SQLスクリプト実行自体にリトライ機構はない。個々のSQL文はSparkの標準リトライに従う。

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

SQLスクリプト全体をアトミックなトランザクションとして管理する仕組みは現時点では提供されない。個々のSQL文のトランザクションはSparkの標準動作に従う。

## パフォーマンス要件

- ステートメント単位で逐次実行されるため、大量のステートメントを含むスクリプトは実行時間に影響
- WHILE/REPEATループの各反復でDataFrameが生成・評価されるためオーバーヘッドに注意

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

- スクリプト内の個々のSQL文はSparkの標準認可メカニズムに従う
- ローカル変数はセッションスコープで管理され、他セッションからアクセス不可

## 備考

- SQLスクリプティングは `sql/core/src/main/scala/org/apache/spark/sql/scripting/` 配下に実装
- Catalyst SQLパーサーがCompoundBody等の論理計画ノードを生成し、scripting配下の実行エンジンがそれを解釈実行する
- カーソル機能（CursorState.scala）は別途実装されている

---

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

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

### 推奨読解順序

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

実行ノードの型階層を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | SqlScriptingExecutionNode.scala | `sql/core/src/main/scala/org/apache/spark/sql/scripting/` | CompoundStatementExec/LeafStatementExec/NonLeafStatementExec トレイト階層 |
| 1-2 | SqlScriptingExecutionContext.scala | `sql/core/src/main/scala/org/apache/spark/sql/scripting/` | SqlScriptingExecutionFrame、フレームスタック管理 |

**読解のコツ**: 実行ノードはLeaf（SingleStatementExec等）とNonLeaf（CompoundBodyExec、IfElseStatementExec等）に分類される。NonLeafノードはIteratorパターンでchildノードを順次返す。

#### Step 2: エントリーポイントを理解する

スクリプト実行のエントリーポイントを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | SqlScriptingExecution.scala | `sql/core/src/main/scala/org/apache/spark/sql/scripting/` | メインクラス、getNextResult/withErrorHandling |

**主要処理フロー**:
1. **41-44行目**: SqlScriptingExecutionクラス定義 - sqlScript, session, argsを受け取る
2. **46-61行目**: コンテキスト初期化 - InterpreterでbuildExecutionPlan → フレーム追加 → enterScope
3. **146-193行目**: `getNextStatement` - フレームスタックから次の実行可能ステートメントを取得。完了フレーム除去、EXIT/CONTINUEハンドラーの後処理
4. **196-214行目**: `getNextResultInternal` - SingleStatementExecをbuildDataFrame()で実行。CommandResult以外なら結果として返却
5. **227-237行目**: `getNextResult` - 例外をキャッチしてhandleExceptionで処理
6. **239-271行目**: `handleException` - findHandlerでハンドラー検索、ハンドラーフレーム追加、完了条件は無視

#### Step 3: 実行計画構築を理解する

InterpreterがCompoundBodyをExecノードに変換する処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | SqlScriptingInterpreter.scala | `sql/core/src/main/scala/org/apache/spark/sql/scripting/` | buildExecutionPlan、transformTreeIntoExecutable |

**主要処理フロー**:
- **50-56行目**: `buildExecutionPlan` - transformTreeIntoExecutable呼び出し
- **70-100行目以降**: `transformBodyIntoExec` - ハンドラーマップ構築、条件別ハンドラー登録

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

```
SqlScriptingExecution(sqlScript, session, args)
    │
    ├─ SqlScriptingInterpreter.buildExecutionPlan(compound, args, ctx)
    │      └─ transformTreeIntoExecutable(compound) → CompoundBodyExec
    │
    ├─ getNextResult()
    │      ├─ getNextStatement()
    │      │      └─ context.frames.last.next()
    │      ├─ stmt.buildDataFrame(session)
    │      └─ handleException(e)
    │             └─ context.findHandler(condition, sqlState)
    │                    └─ ExceptionHandlerExec → 新フレーム追加
    │
    └─ executeSqlScript(session, script, args) [静的メソッド]
           └─ getNextResult() ループ → collect() → LocalRelation
```

### データフロー図

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

CompoundBody            SqlScriptingInterpreter          CompoundBodyExec
(論理計画)        ──▶  buildExecutionPlan           ──▶  (実行ノードツリー)

CompoundBodyExec        SqlScriptingExecution             DataFrame
(実行ノード)      ──▶  getNextResult()              ──▶  (結果セット)
                        ├─ getNextStatement()
                        ├─ buildDataFrame()
                        └─ handleException()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| SqlScriptingExecution.scala | `sql/core/src/main/scala/org/apache/spark/sql/scripting/` | ソース | スクリプト実行メインクラス |
| SqlScriptingInterpreter.scala | `sql/core/src/main/scala/org/apache/spark/sql/scripting/` | ソース | 実行計画構築 |
| SqlScriptingExecutionNode.scala | `sql/core/src/main/scala/org/apache/spark/sql/scripting/` | ソース | 実行ノード定義 |
| SqlScriptingExecutionContext.scala | `sql/core/src/main/scala/org/apache/spark/sql/scripting/` | ソース | 実行コンテキスト・フレーム管理 |
| SqlScriptingContextManagerImpl.scala | `sql/core/src/main/scala/org/apache/spark/sql/scripting/` | ソース | コンテキストマネージャ実装 |
| SqlScriptingLocalVariableManager.scala | `sql/core/src/main/scala/org/apache/spark/sql/scripting/` | ソース | ローカル変数管理 |
| CursorState.scala | `sql/core/src/main/scala/org/apache/spark/sql/scripting/` | ソース | カーソル状態管理 |
