# 機能設計書 81-Expressionスクリプト

## 概要

本ドキュメントは、OpenSearchにおけるLucene Expression言語による高速な数値計算スクリプト機能（lang-expressionモジュール）の設計を記述する。

### 本機能の処理概要

Lucene Expressionは、JavaScript風の数式構文を使用してドキュメントフィールドの数値演算をリアルタイムに実行するスクリプト言語である。Painlessスクリプトと比較して、数値計算に特化しており高速な実行が可能である。

**業務上の目的・背景**：検索時のスコアリング、ソート、集計処理において、複数フィールドの数値を組み合わせた動的な計算が必要となる場面が多い。Expressionはこのような数値計算をコンパイル済みバイトコードとして実行するため、Painlessよりも高速に動作し、大規模データの処理に適している。

**機能の利用シーン**：カスタムスコアリング（例: doc['popularity'].value * 2 + doc['recency'].value）、数値フィールドを組み合わせたソート、バケット集計のセレクタスクリプト、フィルタリング処理などで利用される。

**主要な処理内容**：
1. JavaScript風の数式をLucene JavascriptCompilerでコンパイル
2. コンパイル済みExpressionオブジェクトを各スクリプトコンテキスト（Score, Sort, Aggregation, Filter, Field, TermsSetQuery, BucketAggregation）向けのファクトリに変換
3. 実行時にドキュメントフィールドのDocValuesを変数としてバインドし、数式を評価

**関連システム・外部連携**：Lucene Expressionライブラリ（org.apache.lucene.expressions）に依存。検索クエリ、集計、ソートの各サブシステムと連携する。

**権限による制御**：SpecialPermission.check()によりセキュリティマネージャの下でコンパイルが行われる。AccessController.doPrivilegedを使用してクラスローダの生成が制御される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 143 | スクリプト言語一覧 | 補助機能 | Expression言語の情報提供 |

## 機能種別

計算処理 / スクリプト実行

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| scriptSource | String | Yes | JavaScript風の数式文字列 | JavascriptCompilerによるパース。ParseException時はScriptExceptionを返す |
| scriptName | String | No | スクリプトの識別名 | - |
| context | ScriptContext | Yes | 実行コンテキスト（score, sort, agg等） | サポート外のコンテキストはIllegalArgumentException |
| params | Map<String, String> | No | コンパイルパラメータ | - |

### 入力データソース

- REST APIを通じた検索リクエスト内のscriptフィールド
- ストアドスクリプトからの取得
- インデックスマッピング定義内のscript_scoreクエリ等

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 計算結果 | double | 数式の評価結果（常にdouble型） |
| FilterScript結果 | boolean | フィルタコンテキスト時は0.0以外をtrue |
| BucketAggregationSelector結果 | boolean | 結果が1.0の場合にtrue |

### 出力先

検索スコア、ソートキー、集計値、フィルタ判定結果として各サブシステムに返却される。

## 処理フロー

### 処理シーケンス

```
1. コンパイルフェーズ
   └─ JavascriptCompiler.compile()で数式をExpressionオブジェクトに変換
2. コンテキスト判定
   └─ ScriptContextに対応するファクトリ関数を取得
3. バインディングフェーズ
   └─ 変数（doc['field'], _score, _value, params）をDoubleValuesSourceにバインド
4. 実行フェーズ
   └─ 各ドキュメントに対してExpression.evaluate()を実行し数値を返却
```

### フローチャート

```mermaid
flowchart TD
    A[スクリプトソース受信] --> B[JavascriptCompiler.compile]
    B --> C{コンテキスト判定}
    C -->|ScoreScript| D[newScoreScript]
    C -->|NumberSortScript| E[newSortScript]
    C -->|AggregationScript| F[newAggregationScript]
    C -->|FilterScript| G[newFilterScript]
    C -->|FieldScript| H[newFieldScript]
    C -->|TermsSetQuery| I[newTermsSetQueryScript]
    C -->|BucketAggregation| J[newBucketAggregationScriptFactory]
    D --> K[変数バインディング]
    E --> K
    F --> K
    G --> K
    H --> K
    I --> K
    J --> K
    K --> L{変数種別}
    L -->|_score| M[DoubleValuesSource.SCORES]
    L -->|_value| N[PerThreadReplaceableConstDoubleValueSource]
    L -->|params| O[定数バインド]
    L -->|doc field| P[getDocValueSource]
    P --> Q{フィールド型}
    Q -->|GeoPoint| R[GeoField]
    Q -->|Date| S[DateField/DateObject]
    Q -->|Numeric| T[NumericField]
    M --> U[Expression.evaluate実行]
    N --> U
    O --> U
    R --> U
    S --> U
    T --> U
    U --> V[結果返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 数値型制約 | Expressionは数値型のみを扱い、文字列型フィールドはサポートしない | 全コンテキスト |
| BR-02 | フィールド型制約 | doc['field']で参照可能なフィールドはnumeric, date, geopointのみ | getDocValueSource実行時 |
| BR-03 | パラメータ数値制約 | paramsで渡す変数は数値型のみ | bindFromParams実行時 |
| BR-04 | FilterScript変換 | フィルタコンテキストでは結果が0.0以外の場合にtrueを返す | FilterScript.CONTEXT |
| BR-05 | 決定論的結果 | すべてのコンテキストでisResultDeterministic()はtrueを返す | 全コンテキスト |

### 計算ロジック

数式はLucene JavascriptCompilerによりバイトコードにコンパイルされる。変数はdoc['field'].valueの形式でアクセスし、内部的にはDocValuesから取得したdouble値として評価される。日付フィールドはdoc['field'].date.getYear()等のメソッドアクセスも可能。

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

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

本機能はインデックスデータを直接操作せず、DocValuesの読み取りのみを行う。

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| スクリプト実行 | DocValues | SELECT | フィールドのDocValues値を読み取り |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| ScriptException | compile error | 数式の構文エラー | ParseExceptionの位置情報を含むエラーメッセージを返却 |
| IllegalArgumentException | context error | 未サポートのScriptContext | サポート対象コンテキストを確認 |
| ParseException | link error | 変数バインド時のエラー（存在しないフィールド等） | フィールド名・マッピングを確認 |
| ParseException | field type error | 非数値/日付/地理型フィールドの参照 | 数値・日付・GeoPoint型フィールドのみ使用可能 |
| IllegalArgumentException | param error | paramsに非数値の変数を渡した場合 | 数値型のパラメータのみ指定可能 |

### リトライ仕様

スクリプトのコンパイル・実行はリトライ対象外。エラー時は即座にクライアントにエラーを返却する。

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

本機能は読み取り専用であり、トランザクション管理は不要。

## パフォーマンス要件

ExpressionはLuceneのバイトコードコンパイルを使用するため、Painlessよりも高速に動作する。数値計算に特化しており、JITコンパイルの恩恵を受けやすい。コンパイル結果はScriptServiceによりキャッシュされる。

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

- SpecialPermission.check()によりセキュリティマネージャのチェックが行われる
- AccessController.doPrivilegedを使用してコンパイル処理が実行される
- Expressionは数値計算のみをサポートし、任意のJavaコード実行は不可能
- サンドボックス化されたスクリプト実行環境

## 備考

Expressionは数値演算に特化した軽量なスクリプト言語であり、汎用的なスクリプトが必要な場合はPainlessを使用すること。文字列操作やオブジェクト操作が必要な場合はExpressionでは対応できない。

---

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

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

### 推奨読解順序

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

Expression言語のエンジン構造と、サポートするスクリプトコンテキストの対応関係を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ExpressionScriptEngine.java | `modules/lang-expression/src/main/java/org/opensearch/script/expression/ExpressionScriptEngine.java` | Expressionエンジンのメインクラス。NAME="expression"、staticブロックでcontextsマップを構築 |

**読解のコツ**: 84-164行目のstaticブロックで7つのScriptContextがファクトリ関数と共に登録されている。各コンテキストが異なるファクトリメソッド（newScoreScript, newSortScript等）にマッピングされている点を把握する。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ExpressionModulePlugin.java | `modules/lang-expression/src/main/java/org/opensearch/script/expression/ExpressionModulePlugin.java` | プラグインのエントリーポイント。ScriptPluginインターフェースを実装 |
| 2-2 | ExpressionScriptEngine.java | `modules/lang-expression/src/main/java/org/opensearch/script/expression/ExpressionScriptEngine.java` | compile()メソッド（173-188行目）がスクリプトのコンパイルと実行ファクトリ生成の中心 |

**主要処理フロー**:
1. **43-48行目（Plugin）**: getScriptEngine()でExpressionScriptEngineインスタンスを返却
2. **176-183行目（Engine）**: JavascriptCompiler.compile()で数式をコンパイル
3. **184-187行目（Engine）**: contextsマップからコンテキストに応じたファクトリを取得しキャスト

#### Step 3: 変数バインディングを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ExpressionScriptEngine.java | `modules/lang-expression/src/main/java/org/opensearch/script/expression/ExpressionScriptEngine.java` | getDocValueSource()メソッド（423-509行目）がフィールド変数のバインディングロジック |

**主要処理フロー**:
- **424-464行目**: VariableContext.parse()で変数名をパースし、doc['field'].value形式を解析
- **466-471行目**: MapperServiceからフィールドタイプを取得
- **475-507行目**: フィールド型に応じてGeoField/DateField/NumericFieldのValueSourceを生成

#### Step 4: 各スクリプト実行クラスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | ExpressionScoreScript.java | `modules/lang-expression/src/main/java/org/opensearch/script/expression/ExpressionScoreScript.java` | スコアリング用スクリプト実行クラス |
| 4-2 | ExpressionAggregationScript.java | `modules/lang-expression/src/main/java/org/opensearch/script/expression/ExpressionAggregationScript.java` | 集計用スクリプト実行クラス。_value変数の特殊処理あり |
| 4-3 | ExpressionNumberSortScript.java | `modules/lang-expression/src/main/java/org/opensearch/script/expression/ExpressionNumberSortScript.java` | ソート用スクリプト実行クラス |

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

```
ExpressionModulePlugin.getScriptEngine()
    |
    +-- ExpressionScriptEngine
            |
            +-- compile(scriptSource, context)
            |       |
            |       +-- JavascriptCompiler.compile() [Lucene]
            |       +-- contexts.get(context).apply(expr)
            |
            +-- newScoreScript(expr, lookup, vars)
            |       +-- getDocValueSource(variable, lookup)
            |       |       +-- GeoField.getVariable/getMethod()
            |       |       +-- DateField.getVariable/getMethod()
            |       |       +-- DateObject.getVariable/getMethod()
            |       |       +-- NumericField.getVariable/getMethod()
            |       +-- ExpressionScoreScript
            |
            +-- newSortScript(expr, lookup, vars)
            |       +-- ExpressionNumberSortScript
            |
            +-- newAggregationScript(expr, lookup, vars)
            |       +-- ExpressionAggregationScript
            |
            +-- newFilterScript(expr, lookup, vars)
            |       +-- newScoreScript() [内部委譲]
            |
            +-- newFieldScript(expr, lookup, vars)
            |       +-- ExpressionFieldScript
            |
            +-- newTermsSetQueryScript(expr, lookup, vars)
                    +-- ExpressionTermSetQueryScript
```

### データフロー図

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

スクリプトソース ──────> JavascriptCompiler.compile() ──────> Expression
                                                              |
パラメータ(params) ────> bindFromParams() ──────────────> SimpleBindings
                                                              |
DocValues ──────────> getDocValueSource() ─────────────> DoubleValuesSource
                                                              |
                       Expression.evaluate() <────────── SimpleBindings
                              |
                              v
                        数値結果(double) ──────────> スコア/ソート/集計値
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ExpressionModulePlugin.java | `modules/lang-expression/src/main/java/org/opensearch/script/expression/ExpressionModulePlugin.java` | ソース | プラグインエントリーポイント |
| ExpressionScriptEngine.java | `modules/lang-expression/src/main/java/org/opensearch/script/expression/ExpressionScriptEngine.java` | ソース | スクリプトエンジン本体（コンパイル・バインディング） |
| ExpressionScoreScript.java | `modules/lang-expression/src/main/java/org/opensearch/script/expression/ExpressionScoreScript.java` | ソース | スコアリングスクリプト |
| ExpressionAggregationScript.java | `modules/lang-expression/src/main/java/org/opensearch/script/expression/ExpressionAggregationScript.java` | ソース | 集計スクリプト |
| ExpressionNumberSortScript.java | `modules/lang-expression/src/main/java/org/opensearch/script/expression/ExpressionNumberSortScript.java` | ソース | ソートスクリプト |
| ExpressionFieldScript.java | `modules/lang-expression/src/main/java/org/opensearch/script/expression/ExpressionFieldScript.java` | ソース | フィールドスクリプト |
| ExpressionTermSetQueryScript.java | `modules/lang-expression/src/main/java/org/opensearch/script/expression/ExpressionTermSetQueryScript.java` | ソース | TermsSetQueryスクリプト |
| NumericField.java | `modules/lang-expression/src/main/java/org/opensearch/script/expression/NumericField.java` | ソース | 数値フィールドのValueSource |
| DateField.java | `modules/lang-expression/src/main/java/org/opensearch/script/expression/DateField.java` | ソース | 日付フィールドのValueSource |
| DateObject.java | `modules/lang-expression/src/main/java/org/opensearch/script/expression/DateObject.java` | ソース | 日付オブジェクトのメソッドアクセス |
| GeoField.java | `modules/lang-expression/src/main/java/org/opensearch/script/expression/GeoField.java` | ソース | GeoPointフィールドのValueSource |
| FieldDataValueSource.java | `modules/lang-expression/src/main/java/org/opensearch/script/expression/FieldDataValueSource.java` | ソース | フィールドデータのValueSource基盤 |
| ReplaceableConstDoubleValues.java | `modules/lang-expression/src/main/java/org/opensearch/script/expression/ReplaceableConstDoubleValues.java` | ソース | パラメータ用の置換可能定数 |
| PerThreadReplaceableConstDoubleValueSource.java | `modules/lang-expression/src/main/java/org/opensearch/script/expression/PerThreadReplaceableConstDoubleValueSource.java` | ソース | _value変数用のスレッド別定数 |
