# 画面設計書 10-ドキュメント更新

## 概要

OpenSearchインデックスのドキュメントをスクリプトまたは部分ドキュメントで更新するREST APIエンドポイント（POST /{index}/_update/{id}）の設計書である。ドキュメント全体を再送信せずに、特定のフィールドのみを更新したり、スクリプトによる動的な更新を行う。

### 本画面の処理概要

本APIは、既存ドキュメントに対してスクリプトベースの更新または部分ドキュメントによる差分更新を実行する機能を提供する。

**業務上の目的・背景**：ドキュメントの一部フィールドのみを更新したい場合、ドキュメント登録・更新API（PUT /{index}/_doc/{id}）ではドキュメント全体を再送信する必要がある。本APIは部分更新（partial update）をサポートし、変更が必要なフィールドのみを送信すれば良い。また、Painlessスクリプトを使用してカウンタの増分やフィールド値の計算的な更新が可能である。retry_on_conflictパラメータにより、楽観的排他制御の競合時に自動リトライが可能。

**画面へのアクセス方法**：HTTPクライアントからPOSTメソッドで `/{index}/_update/{id}` にリクエストを送信する。

**主要な操作・処理内容**：
1. クライアントからPOSTリクエストと更新内容（script または doc）を受信する
2. インデックス名、ドキュメントID、各種パラメータを解析する
3. 既存ドキュメントを取得し、更新を適用する
4. 更新後のドキュメントをインデックスに書き戻す
5. 結果をJSON形式で返却する
6. upsert機能により、ドキュメントが存在しない場合は新規作成も可能

**画面遷移**：ドキュメント取得API（GET /{index}/_doc/{id}）で現在の内容を確認した後、本APIで部分更新を行うワークフローが想定される。

**権限による表示制御**：インデックスへの書き込み権限が必要。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 10 | ドキュメントインデックス（登録） | 主機能 | スクリプトまたは部分ドキュメントでドキュメントを更新する処理 |
| 79 | Painlessスクリプト | 補助機能 | スクリプトによる更新時のPainlessスクリプト実行 |

## 画面種別

更新（更新系API）

## URL/ルーティング

| メソッド | パス | 説明 |
|---------|------|------|
| POST | `/{index}/_update/{id}` | ドキュメントを部分更新する |

## 入出力項目

### パスパラメータ

| パラメータ名 | 型 | 必須 | 説明 |
|-------------|-----|------|------|
| index | string | はい | インデックス名 |
| id | string | はい | ドキュメントID |

### クエリパラメータ

| パラメータ名 | 型 | 必須 | デフォルト | 説明 |
|-------------|-----|------|----------|------|
| wait_for_active_shards | string | いいえ | 1 | 処理前にアクティブである必要があるシャードコピー数 |
| _source | list | いいえ | - | レスポンスに含める_sourceフィールドの制御 |
| _source_excludes | list | いいえ | - | _sourceから除外するフィールドリスト |
| _source_includes | list | いいえ | - | _sourceから抽出するフィールドリスト |
| lang | string | いいえ | painless | スクリプト言語 |
| refresh | enum (true, false, wait_for) | いいえ | false | trueでリフレッシュ実行、wait_forでリフレッシュ待機 |
| retry_on_conflict | number | いいえ | 0 | 競合時のリトライ回数 |
| routing | string | いいえ | - | ルーティング値 |
| timeout | time | いいえ | 1m | 操作タイムアウト |
| if_seq_no | number | いいえ | - | 楽観的排他制御用シーケンス番号 |
| if_primary_term | number | いいえ | - | 楽観的排他制御用プライマリターム |
| require_alias | boolean | いいえ | false | trueの場合、宛先がエイリアスであることを要求する |

### リクエストボディ

リクエストボディには `script` または `doc` のいずれかが必須である。

#### スクリプトによる更新

```json
{
  "script": {
    "source": "ctx._source.count += params.count",
    "lang": "painless",
    "params": {
      "count": 1
    }
  }
}
```

#### 部分ドキュメントによる更新

```json
{
  "doc": {
    "field1": "new_value"
  }
}
```

#### Upsert（存在しない場合は新規作成）

```json
{
  "doc": {
    "field1": "new_value"
  },
  "upsert": {
    "field1": "initial_value",
    "counter": 0
  }
}
```

### レスポンスボディ

| フィールド名 | 型 | 説明 |
|-------------|-----|------|
| _index | string | インデックス名 |
| _id | string | ドキュメントID |
| _version | number | ドキュメントバージョン |
| result | string | 操作結果（"updated", "noop", "created"） |
| _shards | object | シャード情報 |
| _shards.total | number | 総シャード数 |
| _shards.successful | number | 成功シャード数 |
| _shards.failed | number | 失敗シャード数 |
| _seq_no | number | シーケンス番号 |
| _primary_term | number | プライマリターム |

## 表示項目

レスポンスのJSON全フィールドが表示項目となる。_sourceパラメータが設定されている場合は、更新後の_sourceフィールドもレスポンスに含まれる。

## イベント仕様

### 1-スクリプトによる更新

1. POST `/{index}/_update/{id}` リクエストを受信する
2. `RestUpdateAction.prepareRequest()` がリクエストパラメータを解析する（73-123行目）
3. `UpdateRequest` オブジェクトを構築する
4. リクエストボディからscriptを解析する（104-118行目）
5. `client.update()` で更新操作を実行する（120-123行目）
6. 既存ドキュメントを取得し、スクリプトを適用して再インデックスする
7. 結果をJSON形式で返却する

### 2-部分ドキュメントによる更新

1. 上記1と同様のフローだが、scriptの代わりにdocフィールドが使用される
2. 既存ドキュメントにdocの内容がマージされる
3. 変更がない場合はresult="noop"が返却される

### 3-Upsert（存在しない場合は新規作成）

1. 上記フローに加え、upsertフィールドが指定されている場合の処理
2. ドキュメントが存在しない場合はupsertの内容で新規作成される
3. 新規作成時はresult="created"が返却される

## データベース更新仕様

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| ドキュメント更新 | 指定インデックス | UPDATE | 既存ドキュメントを更新する |
| Upsert（新規） | 指定インデックス | INSERT | ドキュメントが存在しない場合に新規作成する |

### テーブル別更新項目詳細

#### 指定インデックス

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | _source | scriptまたはdocの適用結果 | 更新後のドキュメント本体 |
| UPDATE | _version | インクリメントされたバージョン番号 | 楽観的排他制御用 |
| UPDATE | _seq_no | 新しいシーケンス番号 | 操作の順序管理 |
| INSERT (upsert) | _source | upsertフィールドの内容 | 新規作成時のドキュメント本体 |

## メッセージ仕様

| メッセージ種別 | HTTPステータス | 条件 | メッセージ内容 |
|--------------|--------------|------|-------------|
| 成功（更新） | 200 OK | ドキュメントが更新された場合 | result: "updated" |
| 成功（変更なし） | 200 OK | 変更が不要だった場合 | result: "noop" |
| 成功（作成） | 201 Created | upsertで新規作成された場合 | result: "created" |
| ドキュメント未存在 | 404 Not Found | ドキュメントが存在せずupsertも指定されていない場合 | document_missing_exception |
| バージョン競合 | 409 Conflict | if_seq_no/if_primary_term不一致時 | VersionConflictEngineException |
| バリデーションエラー | 400 Bad Request | versionまたはversion_typeパラメータが指定された場合 | 内部バージョニングは使用不可メッセージ |

## 例外処理

| 例外 | HTTPステータス | 条件 | レスポンス |
|------|--------------|------|----------|
| DocumentMissingException | 404 | ドキュメントが存在せずupsertも未指定の場合 | document_missing_exception |
| VersionConflictEngineException | 409 | 楽観的排他制御でバージョン不一致の場合 | バージョン競合の詳細メッセージ |
| ActionRequestValidationException | 400 | versionまたはversion_typeが指定された場合 | "internal versioning can not be used for optimistic concurrency control" |
| ScriptException | 400 | スクリプト実行エラーの場合 | スクリプトエラーの詳細 |

## 備考

- Update APIではversionとversion_typeパラメータの使用は禁止されている（RestUpdateAction 91-98行目）。楽観的排他制御にはif_seq_noとif_primary_termを使用する必要がある。
- retry_on_conflictにより、バージョン競合時に指定回数まで自動リトライが可能。これは高並行環境での更新に有用。
- doc_as_upsertをtrueにすると、docの内容がupsertとしても使用される。
- 内部的には、Update APIは「取得→更新適用→再インデックス」のread-modify-writeパターンで処理される。

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | UpdateRequest.java | `server/src/main/java/org/opensearch/action/update/UpdateRequest.java` | リクエストフィールド（script, doc, upsert, retryOnConflict, docAsUpsert等）を理解する |
| 1-2 | UpdateResponse.java | `server/src/main/java/org/opensearch/action/update/UpdateResponse.java` | レスポンス構造（result: UPDATED/NOOP/CREATED）を理解する |

**読解のコツ**: UpdateRequestの `fromXContent()` メソッドでリクエストボディの解析ロジックがわかる。script/doc/upsertの3つの更新モードを理解することが重要。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | RestUpdateAction.java | `server/src/main/java/org/opensearch/rest/action/document/RestUpdateAction.java` | ルーティング（64行目）、パラメータ解析（73-123行目）、version禁止チェック（91-98行目）、リクエストボディ解析（104-118行目）を確認する |

**主要処理フロー**:
1. **64行目**: POSTメソッドで `/{index}/_update/{id}` のルーティングを登録
2. **75行目**: UpdateRequestをindex, idで初期化
3. **77-83行目**: routing, timeout, refresh, wait_for_active_shardsの設定
4. **84行目**: doc_as_upsertの設定
5. **85-88行目**: FetchSourceContextの設定
6. **90行目**: retry_on_conflictの設定
7. **91-98行目**: versionまたはversion_typeが指定された場合のエラーチェック
8. **100-101行目**: if_seq_no, if_primary_termの設定
9. **102行目**: require_aliasの設定
10. **104-118行目**: リクエストボディの解析（script/doc/upsertの取得）
11. **120-123行目**: `client.update()` で更新実行

#### Step 3: トランスポート層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | TransportUpdateAction.java | `server/src/main/java/org/opensearch/action/update/TransportUpdateAction.java` | 取得→更新適用→再インデックスのread-modify-writeパターンを確認する |

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

```
RestUpdateAction.prepareRequest()
    |
    +-- UpdateRequest 構築（パラメータ解析）
    +-- request.applyContentParser() [ボディ解析]
    |       +-- updateRequest.fromXContent(parser)
    |       +-- upsertRequest / doc の構築
    |
    +-- client.update(updateRequest, listener)
            |
            +-- TransportUpdateAction
                    |
                    +-- 1. GET: 既存ドキュメント取得
                    |       +-- ShardGetService.get()
                    |
                    +-- 2. APPLY: 更新適用
                    |       +-- Script実行 (Painless)
                    |       +-- または doc マージ
                    |
                    +-- 3. INDEX: 再インデックス
                            +-- InternalEngine.index()
```

### データフロー図

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

POST /{index}/_update/{id}  --> RestUpdateAction            --> HTTP 200
+ JSON Body                       |                              {
  (script or doc)                 v                                "_index": "...",
                            UpdateRequest構築                       "_id": "...",
                                  |                                "result": "updated",
                                  v                                ...
                            TransportUpdateAction               }
                                  |
                                  v                          --> HTTP 404
                            [GET] 既存ドキュメント取得            (upsert未指定で
                                  |                               ドキュメント未存在時)
                                  v
                            [APPLY] script/doc適用
                                  |
                                  v
                            [INDEX] 更新ドキュメント書込
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| RestUpdateAction.java | `server/src/main/java/org/opensearch/rest/action/document/RestUpdateAction.java` | ソース | RESTハンドラ（エントリーポイント） |
| UpdateRequest.java | `server/src/main/java/org/opensearch/action/update/UpdateRequest.java` | ソース | リクエストオブジェクト |
| UpdateResponse.java | `server/src/main/java/org/opensearch/action/update/UpdateResponse.java` | ソース | レスポンスオブジェクト |
| TransportUpdateAction.java | `server/src/main/java/org/opensearch/action/update/TransportUpdateAction.java` | ソース | トランスポートアクション |
| UpdateHelper.java | `server/src/main/java/org/opensearch/action/update/UpdateHelper.java` | ソース | 更新ヘルパー（script/doc適用ロジック） |
| update.json | `rest-api-spec/src/main/resources/rest-api-spec/api/update.json` | 設定 | REST API仕様定義 |
| RestUpdateActionTests.java | `server/src/test/java/org/opensearch/rest/action/document/RestUpdateActionTests.java` | テスト | ユニットテスト |
