# 機能設計書 79-Painlessスクリプト

## 概要

本ドキュメントは、OpenSearchのPainlessスクリプトモジュール（lang-painless）の機能設計について記述する。Painlessは、OpenSearch専用に設計されたセキュアで高速なスクリプト言語であり、検索スコアリング、ドキュメント更新、インジェスト処理等の様々なコンテキストでスクリプトを実行する機能を提供する。

### 本機能の処理概要

Painlessモジュールは、Java風の構文を持つスクリプト言語のコンパイラとランタイムを提供する。スクリプトはANTLR4で解析され、ASMによりJVMバイトコードに直接コンパイルされて実行される。

**業務上の目的・背景**：検索やデータ処理において、静的な設定だけではカバーできない動的なロジックの実行が必要な場面がある。Painlessは型安全でサンドボックス化されたスクリプト環境を提供し、ユーザーが安全にカスタムロジックを実装できるようにする。

**機能の利用シーン**：
- Script Score（検索スコアのカスタム計算）
- Script Field（動的フィールド計算）
- Update By Query（スクリプトによるドキュメント一括更新）
- インジェストパイプラインのScript Processor
- Derived Fields（派生フィールド）
- Aggregationのスクリプト
- Painless Execute API（スクリプトのテスト実行）

**主要な処理内容**：
1. スクリプトのコンパイル: ANTLR4パーサーでAST生成 → セマンティック解析 → IRツリー生成 → ASMバイトコード生成
2. Allowlistによるアクセス制御: コンテキスト毎に利用可能なクラス/メソッドをAllowlistで制限
3. PainlessExecuteAction: スクリプトのテスト実行用REST API
4. PainlessContextAction: 利用可能なコンテキスト情報取得用REST API
5. 拡張機能サポート: ExtensiblePluginインターフェースによるAllowlistの拡張

**関連システム・外部連携**：ANTLR4（パーサー生成）、ASM（バイトコード生成）、OpenSearch Script Service。

**権限による制御**：スクリプトの実行はScriptServiceを通じて制御される。正規表現の使用はCompilerSettings.REGEX_ENABLEDで制御される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 10 | ドキュメント更新 | 補助機能 | スクリプトによる更新時のPainlessスクリプト実行 |
| 142 | スクリプトコンテキスト一覧 | 主機能 | 全スクリプトコンテキストを返す処理 |
| 143 | スクリプト言語一覧 | 主機能 | 利用可能なスクリプトタイプ・言語・コンテキストを返す処理 |
| 144 | Painlessスクリプト実行 | 主機能 | 任意のPainlessスクリプトを実行し結果を返す処理 |

## 機能種別

スクリプト実行エンジン

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| source | String | Yes | Painlessスクリプトソースコード | Painless構文に準拠 |
| params | Map<String, Object> | No | スクリプトパラメータ | 任意のキー値マップ |
| context | String | No | 実行コンテキスト（score, ingest, update等） | 登録済みコンテキスト名 |

### 入力データソース

REST APIリクエストボディ（Script JSON形式）、またはインデックス設定・検索リクエスト内のscriptフィールド。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| result | Object | スクリプト実行結果（コンテキストにより型が異なる） |

### 出力先

コンテキストに依存。検索スコア（ScoreScript）、変更済みドキュメント（UpdateScript）、インジェスト結果（IngestScript）等。

## 処理フロー

### 処理シーケンス

```
1. スクリプトソースの受信
   └─ ScriptService.compile()呼び出し
2. ANTLRパーサーでPainlessスクリプトをパース
   └─ Walker.buildPainlessTree()でASTを構築
3. セマンティック解析
   └─ PainlessSemanticHeaderPhase → PainlessSemanticAnalysisPhase
4. 最適化
   └─ DefaultConstantFoldingOptimizationPhase → DefaultStringConcatenationOptimizationPhase
5. IRツリー生成
   └─ PainlessUserTreeToIRTreePhase
6. バイトコード生成
   └─ ClassWriter（ASM）でJVMバイトコード生成
7. クラスロード
   └─ SecureClassLoaderでバイトコードをクラスとしてロード
8. スクリプト実行
   └─ コンテキストに応じたスクリプトインスタンスを生成・実行
```

### フローチャート

```mermaid
flowchart TD
    A[スクリプトソース] --> B[ANTLR4 パーサー]
    B --> C[AST 構築]
    C --> D[セマンティック解析]
    D --> E[定数畳み込み最適化]
    E --> F[文字列連結最適化]
    F --> G[IRツリー生成]
    G --> H[ASMバイトコード生成]
    H --> I[SecureClassLoader]
    I --> J[スクリプト実行]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-79-01 | 正規表現制御 | script.painless.regex.enabledでON/OFF制御 | CompilerSettings.REGEX_ENABLED |
| BR-79-02 | 正規表現制限 | script.painless.regex.limit-factorで正規表現の計算量を制限 | CompilerSettings.REGEX_LIMIT_FACTOR |
| BR-79-03 | Allowlistベースアクセス制御 | 各コンテキストで使用可能なクラス/メソッドをAllowlistで制限 | PainlessModulePlugin static initializer（96-128行目） |
| BR-79-04 | コンテキスト別Allowlist | MovingFunction, Score, Ingest, Update, DerivedFieldの各コンテキストに個別Allowlistを適用 | PainlessModulePlugin（98-127行目） |
| BR-79-05 | コードの安全性 | コンパイル済みクラスはUNTRUSTED_CODEBASEで最低権限実行 | Compiler CODESOURCE（74-80行目） |

### 計算ロジック

コンパイルはANTLR4 → AST → セマンティック解析 → 定数畳み込み最適化 → 文字列連結最適化 → IRツリー → バイトコード生成のパイプラインで行われる。

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| スクリプト実行 | - | コンテキスト依存 | Update Scriptの場合はドキュメント更新、Score Scriptの場合はスコア計算のみ |

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

Painless自体は直接的なデータベース操作を行わない。スクリプトの実行結果はコンテキスト（Update, Ingest等）を通じて間接的にインデックスに影響する。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| ScriptException | コンパイルエラー | スクリプト構文エラー | スクリプト構文を修正 |
| PainlessError | ランタイムエラー | スクリプト実行時エラー | スクリプトロジックを修正 |
| PainlessExplainError | デバッグ用 | Debug.explain()呼び出し | デバッグ情報として利用 |
| ClassNotFoundException | Allowlistエラー | 許可されていないクラスの使用 | Allowlistで許可されたクラスのみ使用 |

### リトライ仕様

コンパイルエラーはリトライ不要。ランタイムエラーはスクリプトの修正が必要。

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

スクリプト実行自体はトランザクション管理を行わない。コンテキスト（UpdateByQuery等）のトランザクション管理に委ねる。

## パフォーマンス要件

Painlessスクリプトは初回コンパイル時にバイトコードを生成し、以降はキャッシュされたクラスを再利用する。実行時はネイティブJVMバイトコードとして高速に動作する。正規表現の使用は制限付き（limit-factor）で計算量爆発を防止する。

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

- Allowlistにより使用可能なJavaクラス/メソッドを制限
- SecureClassLoaderで最低権限のコードソースから実行
- 正規表現はデフォルトで無効化可能（DoS防止）
- AccessController.doPrivileged()は最小限のスコープで使用

## 備考

PainlessはScriptPlugin, ExtensiblePlugin, ActionPluginの3つのプラグインインターフェースを実装する。ExtensiblePluginにより外部プラグインからAllowlistを拡張できる。

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | PainlessModulePlugin.java | `modules/lang-painless/src/main/java/org/opensearch/painless/PainlessModulePlugin.java` | プラグインのエントリーポイント。Allowlistの初期設定、ScriptEngine/Action/RestHandlerの登録 |
| 1-2 | CompilerSettings.java | `modules/lang-painless/src/main/java/org/opensearch/painless/CompilerSettings.java` | 正規表現の有効化/制限ファクターの設定定義 |

**読解のコツ**: PainlessModulePluginのstaticイニシャライザ（96-128行目）がコンテキスト別Allowlistの初期設定を行う。loadExtensions()メソッド（172-180行目）で外部プラグインからのAllowlist拡張を受け付ける。

#### Step 2: スクリプトエンジンを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | PainlessScriptEngine.java | `modules/lang-painless/src/main/java/org/opensearch/painless/PainlessScriptEngine.java` | ScriptEngineインターフェースの実装。NAME="painless"。コンテキスト毎にCompilerとPainlessLookupを管理 |

**主要処理フロー**:
- **72行目**: NAME = "painless"
- **87-100行目**: コンストラクタ - コンテキスト毎にPainlessLookupを構築し、Compilerを生成

#### Step 3: コンパイラを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | Compiler.java | `modules/lang-painless/src/main/java/org/opensearch/painless/Compiler.java` | コンパイルパイプラインの実装。ANTLR4パース → セマンティック解析 → 最適化 → バイトコード生成 |

**主要処理フロー**:
- **74-80行目**: CODESOURCE定義（UNTRUSTED_CODEBASE）
- コンパイルフェーズ: Walker → PainlessSemanticHeaderPhase → PainlessSemanticAnalysisPhase → DefaultConstantFoldingOptimizationPhase → DefaultStringConcatenationOptimizationPhase → PainlessUserTreeToIRTreePhase → ClassWriter

#### Step 4: Action/REST APIを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | PainlessExecuteAction.java | `modules/lang-painless/src/main/java/org/opensearch/painless/action/PainlessExecuteAction.java` | /_scripts/painless/_execute APIの実装 |
| 4-2 | PainlessContextAction.java | `modules/lang-painless/src/main/java/org/opensearch/painless/action/PainlessContextAction.java` | /_scripts/painless/_context APIの実装 |

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

```
PainlessModulePlugin (プラグイン登録)
    |
    +-- getScriptEngine() -> PainlessScriptEngine
    |       +-- Compiler (コンテキスト毎)
    |       |       +-- Walker (ANTLR4パーサー)
    |       |       +-- PainlessSemanticHeaderPhase
    |       |       +-- PainlessSemanticAnalysisPhase
    |       |       +-- DefaultConstantFoldingOptimizationPhase
    |       |       +-- DefaultStringConcatenationOptimizationPhase
    |       |       +-- PainlessUserTreeToIRTreePhase
    |       |       +-- ClassWriter (ASMバイトコード生成)
    |       +-- PainlessLookupBuilder (Allowlist構築)
    |
    +-- getActions()
    |       +-- PainlessExecuteAction (スクリプトテスト実行)
    |       +-- PainlessContextAction (コンテキスト情報取得)
    |
    +-- getRestHandlers()
    |       +-- PainlessExecuteAction.RestAction
    |       +-- PainlessContextAction.RestAction
    |
    +-- loadExtensions() -> PainlessExtension (Allowlist拡張)
```

### データフロー図

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

スクリプトソース ───> ANTLR4パース ───> セマンティック解析 ───> 最適化 ───> バイトコード生成 ───> JVM実行
  + パラメータ                                                                                    ↓
  + コンテキスト                                                                              実行結果
                                                                                            (スコア/更新/等)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| PainlessModulePlugin.java | `modules/lang-painless/src/main/java/org/opensearch/painless/PainlessModulePlugin.java` | ソース | モジュールエントリーポイント |
| PainlessScriptEngine.java | `modules/lang-painless/src/main/java/org/opensearch/painless/PainlessScriptEngine.java` | ソース | ScriptEngine実装 |
| Compiler.java | `modules/lang-painless/src/main/java/org/opensearch/painless/Compiler.java` | ソース | コンパイルパイプライン |
| CompilerSettings.java | `modules/lang-painless/src/main/java/org/opensearch/painless/CompilerSettings.java` | ソース | コンパイラ設定 |
| PainlessExecuteAction.java | `modules/lang-painless/src/main/java/org/opensearch/painless/action/PainlessExecuteAction.java` | ソース | テスト実行API |
| PainlessContextAction.java | `modules/lang-painless/src/main/java/org/opensearch/painless/action/PainlessContextAction.java` | ソース | コンテキスト情報API |
| PainlessScript.java | `modules/lang-painless/src/main/java/org/opensearch/painless/PainlessScript.java` | ソース | スクリプトインターフェース |
| Def.java | `modules/lang-painless/src/main/java/org/opensearch/painless/Def.java` | ソース | 動的型ディスパッチ |
| DefBootstrap.java | `modules/lang-painless/src/main/java/org/opensearch/painless/DefBootstrap.java` | ソース | invokedynamicブートストラップ |
| FunctionRef.java | `modules/lang-painless/src/main/java/org/opensearch/painless/FunctionRef.java` | ソース | 関数参照 |
