> ## Documentation Index
> Fetch the complete documentation index at: https://villagesql.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 拡張 API リファレンス

> VDF API のコントラクト、NULL 値の処理、バッファーのサイズ設定、エンコード/デコードの規則、プリラン/ポストラン フック、および VillageSQL 拡張機能の SQL 機能との互換性。

このページは、拡張機能の作成者向けのドキュメントです。ステップバイステップのチュートリアルについては、[拡張機能の作成](/docs/ja/mysql-8.4/0.0.4/create) を参照してください。カスタム列タイプについては、[カスタムタイプの作成](/docs/ja/mysql-8.4/0.0.4/custom-types) を参照してください。

## VDF 関数コントラクト

これらのコントラクトは、VDF 実装関数が VEF ランタイムとどのように連携するかを規定します。`make_func<>` を使用して登録されたすべての関数は、これらに従う必要があります。以下に参照されている型は、`#include <villagesql/vsql.h>` を介して利用できます。

### パート A: VDF 関数コントラクト

**1. VDF 実装関数は `void` 型であり、値を返しません。**

```cpp theme={null}
void my_func_impl(StringArg input, StringResult out) {
    // ... compute result ...
    return;  // always void -- no return value
}
```

成功、NULL、警告、またはエラーは、結果ラッパーのいずれかの終端メソッドを呼び出すことによって伝達されます: `out.set(...)` / `out.set_length(n)`、`out.set_null()`、`out.warning(msg)`、または `out.error(msg)`。

**2. `result->type` を、4 つの結果定数のいずれかに正確に設定します。**

`vef_return_value_type_t` には、次の 4 つの定数があります。

| 定数                   | 値 | 意味                                                                                              |
| -------------------- | - | ----------------------------------------------------------------------------------------------- |
| `VEF_RESULT_VALUE`   | 0 | 成功 - 出力は適切な共用体フィールドにあります                                                                        |
| `VEF_RESULT_NULL`    | 1 | 結果は SQL NULL です                                                                                 |
| `VEF_RESULT_WARNING` | 2 | 行レベルの警告 - 実行は継続され、この行に対して NULL が返され、SQL 警告が追加されます。厳密モードでは、MySQL は INSERT/UPDATE でこれをエラーに昇格させます。 |
| `VEF_RESULT_ERROR`   | 3 | 致命的なエラー - ステートメントの実行が中止されます。メッセージは `result->error_msg` にあります。                                   |

型固有のバリアントはありません。`VEF_RESULT_VALUE` は、文字列、整数、実数、およびカスタム型に対して、成功を示す単一の定数です。出力型は、関数が受け取る結果ラッパー (`StringResult`、`IntResult`、`RealResult`、`CustomResult`) によって決定されます。

**3. `input.value()` を呼び出す前に、`input.is_null()` を確認します。**

`is_null()` が true を返す場合、`value()` を呼び出すと未定義の動作になります。

```cpp theme={null}
void my_func_impl(StringArg input, StringResult out) {
    if (input.is_null()) {
        out.set_null();
        return;
    }
    // Safe to call input.value() -> std::string_view
}
```

**4. 文字列結果の場合、`out.buffer()` に書き込み、`out.set_length(n)` を呼び出します。書き込む前に、`out.buffer().size()` を確認します。**

* `out.buffer()` は、サーバーが管理するバッファーに対する `Span<char>` を返します。
* `out.set_length(n)` は、書き込まれたバイト数を記録します。
* `out.buffer().size()` は、最大容量です。書き込む前に必ず確認してください。

```cpp theme={null}
void upper_impl(StringArg input, StringResult out) {
    if (input.is_null()) {
        out.set_null();
        return;
    }

    auto sv = input.value();
    auto buf = out.buffer();
    if (sv.size() > buf.size()) {
        out.error("Input length exceeds buffer size");
        return;
    }

    for (size_t i = 0; i < sv.size(); i++) {
        buf.data()[i] = toupper(sv[i]);
    }
    out.set_length(sv.size());
}
```

**5. エラーメッセージを `out.error(msg)` に渡します。メッセージは、必要に応じて `VEF_MAX_ERROR_LEN` (512 バイト) に切り捨てられます。**

`out.error(msg)` は `std::string_view` を受け入れます。メッセージをサーバーが管理するバッファーにコピーし、結果の状態をエラーに設定します。

```cpp theme={null}
// Correct — error goes through out.error()
out.error("Invalid input: expected positive integer");
return;
```

## ラッパー関数の実装

実装関数は、型付きの引数と結果ラッパーを使用します。

```cpp theme={null}
#include <villagesql/vsql.h>
#include <algorithm>

using namespace vsql;

// String reverse implementation
void my_reverse_impl(StringArg input, StringResult out) {
    if (input.is_null()) { out.set_null(); return; }

    auto sv = input.value();
    auto buf = out.buffer();
    for (size_t i = 0; i < sv.size(); i++) {
        buf.data()[i] = sv[sv.size() - 1 - i];
    }
    out.set_length(sv.size());
}

// Count vowels implementation
void count_vowels_impl(StringArg input, IntResult out) {
    if (input.is_null()) { out.set_null(); return; }

    long long count = 0;
    for (char c : input.value()) {
        char lower = std::tolower(c);
        if (lower == 'a' || lower == 'e' || lower == 'i' ||
            lower == 'o' || lower == 'u') {
            count++;
        }
    }
    out.set(count);
}
```

## NULL 値の処理

`is_null()` を使用して NULL をチェックし、`set_null()` を呼び出して NULL を返します。

```cpp theme={null}
void my_func_impl(StringArg input, StringResult out) {
    if (input.is_null()) {
        out.set_null();
        return;
    }

    auto sv = input.value();
    auto buf = out.buffer();
    // ... write into buf.data(), up to buf.size() bytes ...

    out.set_length(output_length);
}
```

**NULL 値の処理オプション:**

* **入力 NULL チェック:** `input.is_null()`
* **NULL 値を返す:** `out.set_null()`
* **戻り値:** `out.set(v)` (数値/カスタム) または、文字列の場合、`out.buffer()` に書き込んだ後に `out.set_length(n)`
* **警告を返す:** `out.warning(msg)` - この行に対して NULL を返し、SQL 警告を追加し、実行を継続します。厳密モードでは、MySQL は INSERT/UPDATE でこれをエラーに昇格させます。`out.set()` の代わりに呼び出し、追加としてではなく呼び出します。
* **エラーを返す:** `out.error(msg)` - ステートメントの実行を中止します。

## エラー処理

検証の失敗または無効な入力に対して、カスタムメッセージを使用してエラーを返します。

```cpp theme={null}
void validate_age_impl(IntArg age_input, IntResult out) {
    if (age_input.is_null()) {
        out.set_null();
        return;
    }

    long long age = age_input.value();

    if (age < 0 || age > 150) {
        out.error("Age must be between 0 and 150");
        return;
    }

    out.set(age);
}
```

**結果タイプ:**

* `VEF_RESULT_VALUE` - 成功 (`out.set(v)` / `out.set_length(n)`)
* `VEF_RESULT_NULL` - NULL 値 (`out.set_null()`)
* `VEF_RESULT_WARNING` - 行レベルの警告 (NULL を返し、SQL 警告を追加し、実行を継続します。厳密モードでは、INSERT/UPDATE でエラーに昇格します) (`out.warning(msg)`)
* `VEF_RESULT_ERROR` - 致命的なエラー、ステートメントの実行を中止します (`out.error(msg)`)

## プリラン/ポストランによるステートメントごとの状態

プリランおよびポストラン フックは、型付きラッパーを使用します。必要なシグネチャは次のとおりです。

```cpp theme={null}
void my_prerun(vsql::PrerunArgs args, vsql::PrerunResult out);
void my_postrun(vsql::PostrunArgs args);
```

生の ABI シグネチャ (`vef_prerun_args_t*` / `vef_postrun_args_t*`) は、`.prerun<&Hook>()` および `.postrun<&Hook>()` の `static_assert` によってコンパイル時に拒否されます。

```cpp theme={null}
#include <villagesql/vsql.h>
using namespace vsql;

struct CallCounter { long long n = 0; };

void my_prerun(PrerunArgs, PrerunResult out) {
    out.set_user_data(new CallCounter{});
}

void my_func_impl(CallCounter& state, IntResult out) {
    state.n++;
    out.set(state.n);
}

void my_postrun(PostrunArgs args) {
    args.delete_state<CallCounter>();
}

VEF_GENERATE_ENTRY_POINTS(
  make_extension()
    .func(make_func<&my_func_impl>("my_func")
      .returns(INT).no_params()
      .prerun<&my_prerun>()
      .postrun<&my_postrun>()
      .build())
);
```

`PrerunArgs` および `PostrunArgs` メソッドの詳細については、[ステートメントごとの状態 (プリランおよびポストラン)](/docs/ja/mysql-8.4/0.0.4/development#per-statement-state-prerun-and-postrun) を参照してください。

<注意>
  **ほとんどの拡張機能では、プリラン/ポストラン フックは必要ありません。** VEF SDK は、型チェックや結果バッファーのサイズ設定など、一般的なケースを自動的に処理します。STRING を返す VDF と CUSTOM を返す VDF の両方について、VDF 本体が実行される前に、結果バッファーは解決された戻り値の型に合わせて拡張されます。プリラン/ポストランは、行ごとに発生するべきではない、高価なステートメントごとのセットアップ (接続のオープンなど) が必要な場合にのみ使用してください。

  プリラン/ポストランをユースケースで使用する必要がある場合は、[VillageSQL Discord](https://discord.gg/KSr6whd3Fr) でシナリオを共有してください。チームは、SDK サポートを追加して、これを自動的に処理できるようにする場合があります。
</注意>

## 集計関数

組み込みの集計関数 COUNT(DISTINCT)、MIN、MAX、および GROUP\_CONCAT は、カスタム型でもすぐに使用できます。MIN および MAX には、型に登録された比較関数が必要です。

カスタム集計 VDF もサポートされています。`make_aggregate_func<State, &result_fn>("name")` を使用して登録し、`.returns()`、`.param()`、`.clear<>()`、および `.accumulate<>()` をチェーンしてから、`.build()` を呼び出します。`.clear<>()` と `.accumulate<>()` の両方が必要です。詳細については、[集計 VDF](/docs/ja/mysql-8.4/0.0.4/development#aggregate-vdfs) を参照してください。

**カスタム型を使用した組み込みの集計操作:**

```sql theme={null}
-- COUNT(DISTINCT) works with custom types
SELECT COUNT(DISTINCT impedance) FROM signals;

-- MIN and MAX work with custom types (requires compare function)
SELECT MIN(impedance), MAX(impedance) FROM signals;

-- GROUP_CONCAT works with custom types
SELECT GROUP_CONCAT(impedance ORDER BY impedance SEPARATOR ', ') FROM signals;
```

拡張機能は、1 行ごとの実行モデルで呼び出されます。

* 各関数呼び出しは、独自の結果バッファー (スレッドセーフ) を持つ 1 行を処理します。
* `prerun`/`postrun` は、ステートメントごとのセットアップ/ティアダウンを提供します。
* **グローバル状態を避けてください** - 関数パラメーターと戻り値を使用します。
* グローバル状態を使用する必要がある場合は、ミューテックス/ロックで保護します。

**ベストプラクティス:** シンプルさと安全性のために、ステートレスな関数を設計します。

## ウィンドウ関数

次のウィンドウ関数は、カスタム型で動作します。

```sql theme={null}
SELECT
    id,
    impedance,
    LAG(impedance)  OVER (ORDER BY id) AS prev_impedance,
    LEAD(impedance) OVER (ORDER BY id) AS next_impedance
FROM signals;

SELECT
    id,
    impedance,
    FIRST_VALUE(impedance) OVER w AS first_impedance,
    LAST_VALUE(impedance)  OVER w AS last_impedance,
    NTH_VALUE(impedance, 2) OVER w AS second_impedance
FROM signals
WINDOW w AS (ORDER BY id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING);
```

## 一時テーブル

カスタム型は、一時テーブルで動作します。`CREATE TEMPORARY TABLE`、`INSERT`、および `ALTER TABLE` は、永続テーブルと同じように動作します。

```sql theme={null}
CREATE TEMPORARY TABLE tmp_signals (
    id        INT PRIMARY KEY,
    impedance COMPLEX
);

INSERT INTO tmp_signals VALUES (1, '(10,5)'), (2, '(20,0)');
SELECT id, impedance FROM tmp_signals;
```

## プレビュー API

一部の VEF 機能は、SDK インクルードツリーの `villagesql/preview/` の下にあるオプトインヘッダーとして利用できます。ABI と API はまだ積極的に開発されており、予告なしに変更される可能性があります。

オプトインするには、拡張機能のソースにインクルードを追加します。たとえば、次のとおりです。

```cpp theme={null}
#include <villagesql/preview/keyring.h>        // vsql::preview_keyring::KeyringCapability
#include <villagesql/preview/thread_worker.h>  // vsql::preview_thread_worker::ThreadWorkerCapability
#include <villagesql/preview/sql_query.h>      // vsql::preview_sql_query::SqlQueryCapability
```

これらのヘッダーは、`<villagesql/vsql.h>` によって取り込まれません。オプトインする場合は、これを直接インクルードする必要があります。

`vsql::preview` の下の名前空間レイアウトは、機能ごとです。単一の普遍的なパターンはありません。キーリング API は `vsql::preview_keyring::KeyringCapability` を使用します。スレッドワーカー API は `vsql::preview_thread_worker::ThreadWorkerCapability` を使用します。SQL クエリ API は `vsql::preview_sql_query::SqlQueryCapability` を使用し、バックグラウンドワーカー スレッドハンドル (`vef_thread_handle_t *`) から取得する必要があります。各ヘッダーを確認して、定義されている正確な名前空間とクラス名を確認してください。

完全なプレビュー API ドキュメントについては、[プレビュー機能](/docs/ja/mysql-8.4/0.0.4/preview-capabilities) を参照してください。

<警告>
  プレビューヘッダーは安定していません。それらを使用してビルドされた拡張機能は、サーバーが更新されたときに破損する可能性があります。機能が安定すると、そのヘッダーはバージョン指定された安定した SDK パスに移動します。
</警告>

## トリガー

トリガーは、カスタム型の列を持つテーブルで発動します。トリガー本体は、`NEW` および `OLD` からのカスタム型以外の列を参照できます。トリガー本体内でカスタム型の列の値にアクセスすることは、まだサポートされていません。

```sql theme={null}
CREATE TABLE signals (
    id        INT PRIMARY KEY,
    impedance COMPLEX,
    label     VARCHAR(50)
);
CREATE TABLE signal_log (
    id        INT,
    label     VARCHAR(50),
    logged_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TRIGGER signals_after_insert
AFTER INSERT ON signals
FOR EACH ROW
    INSERT INTO signal_log (id, label) VALUES (NEW.id, NEW.label);

INSERT INTO signals VALUES (1, '(10,5)', 'sensor_a');
SELECT id, label FROM signal_log;
```
