# 通知設計書 51-DropTableEvent

## 概要

本ドキュメントは、Apache Sparkの外部カタログにおいてテーブルが削除された後に発火するイベント通知「DropTableEvent」の設計仕様を記述する。本イベントは `ExternalCatalogEvent` の一種であり、テーブル削除操作の完了を外部リスナーに通知するために使用される。

### 本通知の処理概要

DropTableEventは、外部カタログ（ExternalCatalog）上でテーブルの削除操作が正常に完了した直後に発火するイベント通知である。ExternalCatalogWithListenerクラスがデリゲートパターンを用いて実際のカタログ操作をラップし、操作完了後にListenerBusを通じて登録済みリスナーへイベントを配信する。

**業務上の目的・背景**：Sparkのカタログシステムでは、テーブルのライフサイクル管理が重要であり、テーブルが削除された際にキャッシュの無効化、UIの更新、監査ログの記録、依存テーブルの整合性チェックなどの後続処理を実行する必要がある。DropTableEventはこれらの後続処理をイベント駆動で実現するための基盤となる。

**通知の送信タイミング**：ExternalCatalogWithListener.dropTableメソッドにおいて、デリゲート先のExternalCatalog.dropTableが正常に完了した直後（行105）にpostToAllメソッドで発火される。テーブルが存在しない場合でもignoreIfNotExists=trueであれば発火する。

**通知の受信者**：ExternalCatalogEventListenerインタフェースを実装し、ExternalCatalogWithListenerに登録された全てのリスナーが受信する。SharedStateの初期化時にSparkContext.listenerBusへのブリッジリスナーが登録されるため、SparkListenerBus上の全リスナーにも伝播される。

**通知内容の概要**：削除対象テーブルが属するデータベース名（database）とテーブル名（name）の2つのフィールドを含む。TableEventトレイトおよびDatabaseEventトレイトを継承しており、SparkListenerEventとしても扱われる。

**期待されるアクション**：受信リスナーはテーブル削除の完了を検知し、関連するキャッシュの破棄、メタデータの更新、UIの表示更新、イベントログへの記録などの後続処理を実行する。

## 通知種別

アプリ内通知（Sparkイベントバス経由のインプロセスイベント配信）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（postToAllによるリスナーバス内同期配信） |
| 優先度 | 中（通常のカタログ操作イベント） |
| リトライ | なし（同期配信のため、リスナー側の例外はログ出力後に無視） |

### 送信先決定ロジック

ExternalCatalogWithListenerに `addListener` メソッドで登録された全てのExternalCatalogEventListenerに対して配信される。SharedState初期化時（SharedState.scala 行175）にSparkContext.listenerBusへpostするブリッジリスナーが登録され、Spark全体のリスナーバスにも伝播する。送信先のフィルタリングは行われず、登録済み全リスナーが受信対象となる。

## 通知テンプレート

### メール通知の場合

本イベントはインプロセスのイベントバス配信であり、メール通知は該当しない。

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | N/A |
| 送信元名称 | N/A |
| 件名 | N/A |
| 形式 | N/A |

### 本文テンプレート

```
イベントはcase classのインスタンスとして配信される：
DropTableEvent(database: String, name: String)

例：DropTableEvent("default", "my_table")
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| N/A | N/A | N/A | インプロセスイベントのため添付ファイルなし |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| database | テーブルが属するデータベース名 | ExternalCatalogWithListener.dropTableの引数db | Yes |
| name | 削除されたテーブル名 | ExternalCatalogWithListener.dropTableの引数table | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| API呼び出し | ExternalCatalogWithListener.dropTable() | デリゲート先のdropTableが例外なく完了すること | 行104でdelegate.dropTableが成功した後、行105でpostToAllされる |
| API呼び出し | DROP TABLE SQL文の実行 | SQLパーサー経由でdropTableが呼ばれること | SparkSQL経由でもdropTable APIを通じて発火する |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| テーブルが存在せずignoreIfNotExists=false | delegate.dropTableが例外をスローし、DropTableEventは発火しない（DropTablePreEventのみ発火） |
| デリゲート処理で例外発生 | delegate.dropTableが何らかの例外をスローした場合、PostイベントであるDropTableEventは発火しない |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[dropTable呼び出し] --> B[DropTablePreEvent発火]
    B --> C[delegate.dropTable実行]
    C -->|成功| D[DropTableEvent発火]
    C -->|例外| E[例外スロー - DropTableEventは未発火]
    D --> F[postToAllで全リスナーに配信]
    F --> G[SparkListenerBusへ伝播]
    G --> H[終了]
    E --> H
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| メタストアのテーブルメタデータ | 削除対象テーブルの存在確認 | Hiveメタストアまたはインメモリカタログ |

### テーブル別参照項目詳細

#### メタストアテーブルメタデータ

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| database_name | テーブル所属DB確認 | 引数dbで指定 |
| table_name | テーブル存在確認 | 引数tableで指定 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| メタストアのテーブルメタデータ | DELETE | デリゲート先のExternalCatalog実装がテーブルメタデータを削除 |

#### 送信ログテーブル

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| N/A | N/A | N/A | イベントバス配信のため送信ログテーブルは存在しない。EventLoggingListenerによりイベントログファイルに記録される可能性がある |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| テーブル不存在エラー | テーブルが存在せずignoreIfNotExists=falseの場合 | DropTablePreEventのみ発火し、DropTableEventは発火しない。呼び出し元に例外がスローされる |
| リスナー例外 | リスナーのonEvent内で例外が発生した場合 | ListenerBusがキャッチしてログ出力。他のリスナーへの配信は継続される |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0（リトライなし） |
| リトライ間隔 | N/A |
| リトライ対象エラー | N/A（同期配信のためリトライ機構なし） |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし。dropTable APIが呼び出されたタイミングで即座に配信される。

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

- DropTableEventはSparkListenerEventを継承しているため、SparkListenerBusに登録された全てのリスナーに配信される。リスナー登録はSparkアプリケーション内部のAPIを通じて行われるため、外部からの不正アクセスリスクは低い。
- イベントにはデータベース名とテーブル名のみが含まれ、テーブルデータ自体は含まれないため、機密データの漏洩リスクは限定的である。
- EventLoggingListenerが有効な場合、イベントログファイルにテーブル名が記録されるため、ログファイルのアクセス制御に留意する必要がある。

## 備考

- DropTableEventはテーブル削除の「後」イベント（post-event）であり、対応する「前」イベントとしてDropTablePreEvent（No.50）が存在する。
- ignoreIfNotExists=trueの場合、テーブルが存在しなくてもデリゲート処理が正常完了するため、DropTableEventが発火する（テストコード ExternalCatalogEventSuite.scala 行160-161で確認）。
- case classとして定義されているため、パターンマッチングで容易にイベント種別を識別できる。

---

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

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

### 推奨読解順序

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

まず、イベントの型階層とデータ構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | events.scala | `sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/events.scala` | 行26: ExternalCatalogEventトレイト（SparkListenerEvent継承）、行38-43: DatabaseEventトレイト（databaseフィールド）、行78-83: TableEventトレイト（nameフィールド追加）、行100-103: DropTableEventケースクラスの定義 |

**読解のコツ**: Scalaのcase classはコンストラクタ引数がそのまま不変フィールドになる。traitの継承階層 `SparkListenerEvent -> ExternalCatalogEvent -> DatabaseEvent -> TableEvent -> DropTableEvent` を理解することで、イベントが持つフィールドの全体像を把握できる。

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

処理の起点となるExternalCatalogWithListenerのdropTableメソッドを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ExternalCatalogWithListener.scala | `sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/ExternalCatalogWithListener.scala` | 行27-29: クラス定義（デリゲートパターンとListenerBusミックスイン）、行34-38: doPostEventメソッド（リスナーへの配信処理）、行98-106: dropTableメソッド（Pre/Postイベントの発火） |

**主要処理フロー**:
1. **行103**: `postToAll(DropTablePreEvent(db, table))` でPreイベントを発火
2. **行104**: `delegate.dropTable(db, table, ignoreIfNotExists, purge)` でデリゲート先の実処理を実行
3. **行105**: `postToAll(DropTableEvent(db, table))` でPostイベント（本イベント）を発火

#### Step 3: イベント配信基盤を理解する

ListenerBusによるイベント配信メカニズムを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ListenerBus.scala | `core/src/main/scala/org/apache/spark/util/ListenerBus.scala` | 行37: ListenerBusトレイト定義、行39: CopyOnWriteArrayListによるスレッドセーフなリスナー管理、行67-69: addListenerメソッド |

**主要処理フロー**:
- **行37**: `trait ListenerBus[L <: AnyRef, E]` でジェネリックなイベントバスを定義
- **行67-69**: `addListener` でリスナーをスレッドセーフに追加
- postToAll（ListenerBusの親トレイト由来）で全リスナーにイベントを同期配信

#### Step 4: SharedStateでの初期化を理解する

SparkSessionの共有状態でExternalCatalogWithListenerがどのように初期化されるかを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | SharedState.scala | `sql/core/src/main/scala/org/apache/spark/sql/internal/SharedState.scala` | 行149-178: externalCatalogのlazy val定義、行172: ExternalCatalogWithListenerのインスタンス化、行175: SparkContext.listenerBusへのブリッジリスナー登録 |

**主要処理フロー**:
- **行150-151**: リフレクションでExternalCatalog実装（HiveExternalCatalogまたはInMemoryCatalog）を生成
- **行172**: `new ExternalCatalogWithListener(externalCatalog)` でラッパーを生成
- **行175**: `wrapped.addListener((event) => sparkContext.listenerBus.post(event))` でSparkListenerBusへブリッジ

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

```
SparkSession (SQL: DROP TABLE)
    |
    +-- SessionCatalog.dropTable()
           |
           +-- ExternalCatalogWithListener.dropTable()  [行98-106]
                  |
                  +-- postToAll(DropTablePreEvent)       [行103]
                  |      |
                  |      +-- ExternalCatalogEventListener.onEvent()
                  |      +-- SparkContext.listenerBus.post()  [SharedState 行175]
                  |
                  +-- delegate.dropTable()               [行104]
                  |      |
                  |      +-- InMemoryCatalog.dropTable() or HiveExternalCatalog.dropTable()
                  |
                  +-- postToAll(DropTableEvent)          [行105]
                         |
                         +-- ExternalCatalogEventListener.onEvent()
                         +-- SparkContext.listenerBus.post()  [SharedState 行175]
```

### データフロー図

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

dropTable(db, table,     --> ExternalCatalogWithListener     --> DropTablePreEvent
 ignoreIfNotExists,              .dropTable()                     (database, name)
 purge)                          |                                    |
                                 v                                    v
                           delegate.dropTable()               --> DropTableEvent
                           (実際のメタストア操作)                   (database, name)
                                                                      |
                                                                      v
                                                              SparkListenerBus
                                                              (全リスナーに伝播)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| events.scala | `sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/events.scala` | ソース | DropTableEventを含む全ExternalCatalogEventの定義 |
| ExternalCatalogWithListener.scala | `sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/ExternalCatalogWithListener.scala` | ソース | イベント発火を伴うカタログ操作のラッパー |
| ExternalCatalog.scala | `sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/catalog/ExternalCatalog.scala` | ソース | 外部カタログのインタフェース定義 |
| ListenerBus.scala | `core/src/main/scala/org/apache/spark/util/ListenerBus.scala` | ソース | イベント配信基盤 |
| SharedState.scala | `sql/core/src/main/scala/org/apache/spark/sql/internal/SharedState.scala` | ソース | ExternalCatalogWithListenerの初期化とSparkListenerBusへの接続 |
| ExternalCatalogEventSuite.scala | `sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/catalog/ExternalCatalogEventSuite.scala` | テスト | DropTableEventの発火テスト（行151-161） |
