> ## 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.

# 開発

> VillageSQL 拡張機能の拡張関数の作成とテストの実行

このガイドでは、VillageSQL 拡張機能の VDF 実装の作成と回帰テストの実行について解説します。これは、エンドツーエンドのビルド手順をカバーする [Creating Extensions](/docs/ja/mysql-8.4/0.0.4/create) の補完資料です。

<Warning>
  VEF Protocol 3 は v0.0.4 で安定版となりました。Protocol 4 は開発中で、オプトイン形式の dev ABI ヘッダー（`-DVSQL_USE_DEV_ABI=ON`）経由でのみ利用可能です。古い Protocol 2 に対してビルドされた拡張機能はサーバーによって拒否され、再ビルドが必要です。
</Warning>

<Note>
  VillageSQL サーバー自体に貢献する場合（拡張機能のビルドではない場合）は、`mysql-test-run.pl` を直接使用してテストを実行するなど、サーバー開発者のワークフロー全体をカバーする [Build from Source](/docs/ja/mysql-8.4/0.0.4/source) を参照してください。
</Note>

## 環境のセットアップ

拡張機能の開発とテストには、ビルド済みの VillageSQL サーバーが必要です。サーバーバイナリをコンパイルするには、[Clone and Build from Source](/docs/ja/mysql-8.4/0.0.4/source) ガイドに従ってください。

ビルドが完了したら、`villagesql` CLI を使用してローカルの開発サーバーインスタンスを管理します。すべてのコマンドは VillageSQL がインストールされたディレクトリから実行してください。

### ローカル開発サーバーの起動

サーバーインスタンスを初期化して起動します：

```bash theme={null}
./villagesql init    # initialize database and seed bundled extensions
./villagesql start   # start the server (default port 3307)
./villagesql status  # check the server is running
./villagesql connect # open a mysql shell
./villagesql stop    # stop the server
```

初期化時に root パスワードを設定するには：

```bash theme={null}
./villagesql init --password
./villagesql start
```

複数の独立したインスタンスを管理するには、コマンドの前に `--dir <path>` を渡すか、`--here` を使用して現在の作業ディレクトリにサーバーディレクトリを作成します：

```bash theme={null}
./villagesql --here init
./villagesql --here start
```

### 拡張機能ファイルの管理

SQL を介して拡張機能をインストールする前に、その `.veb` ファイルがサーバー上に存在している必要があります。CLI はサーバーの `lib/veb/` ディレクトリを管理します：

```bash theme={null}
./villagesql veb add /path/to/my_extension.veb  # copy a .veb to the server
./villagesql veb ls                              # list available .veb files
./villagesql veb rm my_extension                # remove a .veb file
```

`init` の前に `lib/veb/` に配置された `.veb` ファイルは自動的にシードされます。ファイルを追加した後、SQL を介して拡張機能をインストールします：

```sql theme={null}
INSTALL EXTENSION my_extension;
```

## 拡張機能関数の作成

拡張機能関数は C++ で記述され、VEF に登録されます。SDK に完全にアクセスするには、単一のヘッダーをインクルードしてください：

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

<h3 id="typed-wrappers-recommended">
  型付きラッパー（推奨）
</h3>

型付きラッパーは、VDF のパラメータと結果に対して型安全なインターフェースを提供します。
フレームワークは関数シグネチャ内のラッパー型を検出し、自動的に適応します — `make_func` の登録構文は変更されません。

**入力ラッパー:** `IntArg`, `RealArg`, `StringArg`, `CustomArg` — 各ラッパーは `is_null()` と `value()` を提供します。パラメータ化されたカスタム型の場合、`CustomArgWith<P>` はキャッシュされた解析済みの params 構造体を返す `params()` アクセサを追加します（[Parameterized Types](#parameterized-types) を参照）。

**結果ラッパー:** `IntResult`, `RealResult`, `StringResult`, `CustomResult`
— 各ラッパーは `set_null()`, `warning(msg)`, `error(msg)` を提供します。スカラー結果には `set(value)` も提供されます。バッファ結果には `buffer()` と `set_length(len)` が提供されます。`StringResult` にはさらに `set(std::string_view)` が提供され、ビューから最大 `buffer().size()` バイトをコピーして長さを一度に設定します。パラメータ化されたカスタム型の場合、`CustomResultWith<P>` は `params()` アクセサを追加します。

**Span 型:** バイト指向のラッパーにおける `value()` と `buffer()` は `vsql::Span<T>` を返します — `data()`, `size()`, `empty()`, `begin()`/`end()`, `operator[]` を持つ、連続した `T` の範囲に対する非所有ビューです。C++20 では `std::span<T>` のエイリアスであり、C++17 では SDK が最小限の互換実装を提供するため、どちらの標準でも同じコードがコンパイルされます。`<villagesql/vsql.h>` を介して利用可能です。

`warning(msg)` は行に対して SQL NULL を返し、SQL 警告を追加します。厳格モード（`STRICT_TRANS_TABLES`）では、MySQL はこれを INSERT/UPDATE 時のステートメントエラーに昇格させるため、厳格なコンテキストでは `error(msg)` と同様に動作します。エンコード関数で解析できない文字列など、回復可能な不正な入力に使用してください。破損した保存データや、続行が安全でないあらゆる条件には `error(msg)` を使用してください。両方のメッセージは必要に応じてサーバーの内部エラーバッファに収まるように切り捨てられます。

**スカラーの例** — 2 つの整数を加算：

```cpp theme={null}
using namespace vsql;

void add_impl(IntArg a, IntArg b, IntResult out) {
  if (a.is_null() || b.is_null()) { out.set_null(); return; }
  out.set(a.value() + b.value());
}

// Registration is unchanged:
make_func<&add_impl>("add").returns(INT).param(INT).param(INT).build();
```

**バイナリの例** — カスタム型バッファをインプレースで変換：

```cpp theme={null}
using namespace vsql;

void rot13_impl(CustomArg in, CustomResult out) {
  if (in.is_null()) { out.set_null(); return; }
  auto src = in.value();   // vsql::Span<const unsigned char>
  auto dst = out.buffer(); // vsql::Span<unsigned char>
  for (size_t i = 0; i < src.size(); i++) { dst[i] = transform(src[i]); }
  out.set_length(src.size());
}
```

`StringResult` と `CustomResult` の場合、`buffer()` に書き込み、その後書き込んだバイト数で `set_length()` を呼び出します。`buffer().size()` が最大容量です。

カスタム型を返す VDF（`returns(CUSTOM(MYTYPE))`）の場合、サーバーは結果バッファのサイズを解決された戻り型の `persisted_length` に自動的に合わせます — 拡張機能作者はこの場合、関数ビルダーで `.buffer_size(...)` を宣言する必要はありません。`prerun` がバッファをさらに拡張する場合、その大きなサイズは保持されます。これにより、例えば `SVECTOR::from_string('[…1024 floats…]')` が結果ラッパーの領域不足なしでワイドベクトルをエンコードできます。

同じ拡張機能内の関数間で異なるスタイルを使用できます — 各関数のスタイルはそれぞれのシグネチャによって決定されます。

<h3 id="aggregate-vdfs">
  集計 VDF
</h3>

集計 VDF は各 `GROUP BY` グループ内の行にわたって状態を蓄積し、SQL の `SUM` や `COUNT` のようにグループごとに 1 つの結果を返します。登録には `make_aggregate_func<State, &result_fn>("name")` を使用します。State 型はグループごとの蓄積バッファであり、`prerun` と `postrun` はそれを割り当てて削除するために自動生成されます。

結果関数は `void(const State&, ResultWrapper)` のシグネチャを持つ必要があり、`ResultWrapper` は `IntResult`, `RealResult`, `StringResult`, `CustomResult`, または `CustomResultWith<P>` のいずれかです。値を返すには `out.set(value)` を、SQL NULL を返すには `out.set_null()` を呼び出します。

`.clear<>()` と `.accumulate<>()` の両方が必須です。ビルダーはこれをコンパイル時に（`build()` を介して）強制し、サーバーは `INSTALL EXTENSION` 時に再度検証します — `clear` は状態をリセットし、`accumulate` は行を折りたたみ、結果関数は最終状態を読み取ります。

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

using namespace vsql;

// State type: nullopt means no non-NULL rows seen yet.
using SumState = std::optional<long long>;

void my_clear(SumState &s) { s = std::nullopt; }
void my_acc(SumState &s, IntArg v) {
  if (!v.is_null()) s = s.value_or(0) + v.value();
}
void my_result(const SumState &s, IntResult out) {
  if (!s.has_value()) { out.set_null(); return; }
  out.set(s.value());
}

// Registration:
// make_aggregate_func<SumState, &my_result>("my_sum")
//     .returns(INT)
//     .param(INT)
//     .clear<&my_clear>()
//     .accumulate<&my_acc>()
//     .build()
```

ビルダーメソッドの動作：

* `make_aggregate_func<State, &result_fn>()` は `prerun` と `postrun` を自動生成します（`State` の値初期化と削除）。
* `.clear<&fn>()` は `void(State&)` を `vef_vdf_clear_func_t` にラップします
* `.accumulate<&fn>()` は `void(State&, TypedArgs...)` を `vef_vdf_accumulate_func_t` にラップします。`TypedArgs` は関数シグネチャ（`IntArg`, `StringArg` など）から推論されます。
* `ResultWrapper` 型（`IntResult`, `RealResult` など）は結果関数のシグネチャから推論されます。

NULL を返さないカウンターの場合、プレーンな状態型を使用します：

```cpp theme={null}
using CountState = long long;
void count_clear(CountState &s) { s = 0; }
void count_acc(CountState &s, IntArg v) { if (!v.is_null()) s++; }
void count_result(const CountState &s, IntResult out) { out.set(s); }
```

<h3 id="per-statement-state-prerun-and-postrun">
  ステートメントごとの状態（Prerun と Postrun）
</h3>

一部の VDF では、単一のクエリが接触するすべての行にわたる状態が必要です — 呼び出しカウンター、キャッシュされた結果、オープンリソースなど。**prerun** フックで割り当て、VDF 本体からアクセスし、**postrun** フックで解放します。両方のフックはステートメントごとに 1 回実行され、VDF 本体は行ごとに 1 回実行されます。

`.prerun<&Hook>()` と `.postrun<&Hook>()` で登録します。必要なシグネチャは次の通りです：

| Hook    | Required signature                           |
| ------- | -------------------------------------------- |
| Prerun  | `void(vsql::PrerunArgs, vsql::PrerunResult)` |
| Postrun | `void(vsql::PostrunArgs)`                    |

生の ABI シグネチャはコンパイル時に拒否されます。状態を保存するには `PrerunResult::set_user_data(void*)` を使用し、解放するには `PostrunArgs::delete_state<T>()` を使用します。prerun が `set_user_data(new T{})` を呼び出す場合、postrun は **必ず** `delete_state<T>()` を呼び出す必要があります — SDK は自動解放しません。

`PrerunArgs::type_at(i)` は、行が読み込まれる前に各引数の宣言された SQL 型を公開します。返される `PrerunArgType` 上の述語 `is_int()`, `is_real()`, `is_str()`, `is_custom()` は列型を反映します。これを prerun で引数型の検証に使用するか、結果バッファのサイズ設定に `PrerunResult::request_buffer_size(n)` を呼び出します。

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

struct CallCounter { long long n = 0; };

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

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

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

// Registration:
// make_func<&ba_call_index>("ba_call_index")
//     .returns(INT).no_params()
//     .prerun<&ba_call_index_prerun>()
//     .postrun<&ba_call_index_postrun>()
//     .build()
```

### 可変長引数 VDF

**可変長引数** VDF は、任意の SQL 型の任意の数の引数を受け入れます。func ビルダーで `.varargs()` を宣言し、これは `.no_params()` および `.param(TYPE)` と排他です。本体は通常の固定引数ラッパーの代わりに `vsql::VarArgs` 引数を受け取ります。

<Warning>
  可変長引数の登録には VEF Protocol 3 が必要です。古いサーバーはインストール時に拡張機能を拒否します。
</Warning>

フレームワークは可変長引数 VDF の引数カウントや型を検証できません。すべての可変長引数登録には、不正な入力時に `PrerunResult::error()` を呼び出すか、結果バッファのサイズ設定に `PrerunResult::request_buffer_size(n)` を呼び出す prerun フックをペアにしてください。

範囲 for ループで引数を反復処理します。各 `AnyArg` 要素は値を読み取る前に型チェックが必要です：

| Predicate     | Accessor      | Return type                       |
| ------------- | ------------- | --------------------------------- |
| `is_int()`    | `as_int()`    | `long long`                       |
| `is_real()`   | `as_real()`   | `double`                          |
| `is_str()`    | `as_str()`    | `std::string_view`                |
| `is_custom()` | `as_custom()` | `vsql::Span<const unsigned char>` |

どのアクセサでも `is_null()` をチェックしてください — 4 つすべてで null 引数に対する動作は未定義です。

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

constexpr size_t kBytearrayLen = 4;

void ba_concat_all_prerun(PrerunArgs args, PrerunResult out) {
  if (args.size() == 0) {
    out.error("ba_concat_all requires at least one argument");
    return;
  }
  for (size_t i = 0; i < args.size(); i++) {
    auto t = args.type_at(i);
    if (!t.is_custom() && !t.is_str()) {
      out.error("ba_concat_all: argument " + std::to_string(i) +
                " must be BYTEARRAY");
      return;
    }
  }
  out.request_buffer_size(args.size() * kBytearrayLen);
}

void ba_concat_all(VarArgs args, StringResult out) {
  auto dst = out.buffer();
  size_t off = 0;
  for (auto a : args) {
    if (a.is_null() || !a.is_custom()) { out.set_null(); return; }
    auto bytes = a.as_custom();
    std::memcpy(dst.data() + off, bytes.data(), bytes.size());
    off += bytes.size();
  }
  out.set_length(off);
}

// Registration:
// make_func<&ba_concat_all>("ba_concat_all")
//     .returns(STRING).varargs()
//     .prerun<&ba_concat_all_prerun>()
//     .build()
```

### VEF\_GENERATE\_REGISTRATION

`VEF_GENERATE_REGISTRATION` は拡張機能登録を実行しますが `extern "C"` エントリポイント を定義しない内部ヘルパー `_vef_do_register()` を作成します。テストビルドで登録後に記述子をパッチするなど、`vef_register` の動作をカスタマイズする必要がある場合に使用してください。通常の拡張機能には代わりに `VEF_GENERATE_ENTRY_POINTS` を使用します。

```cpp theme={null}
VEF_GENERATE_REGISTRATION(
    make_extension()
        .func(make_func<&my_impl>("my_func").returns(INT).build()))

// Then define your own extern "C" vef_register/vef_unregister that call
// _vef_do_register() and optionally modify the result.
```

<h3 id="type-operation-builders">
  型演算ビルダー
</h3>

*拡張機能がカスタム列型を定義する場合のみ必要です。関数のみを作成している場合は、[Running Regression Tests](#running-regression-tests) にスキップしてください。*

カスタム型には、エンジンが内部で呼び出す 3 つの演算が必要です：エンコード（文字列からバイナリ）、デコード（バイナリから文字列）、比較。ハッシュはオプションです。これらの C++ シグネチャに対して実装してください（すべて `<villagesql/vsql.h>` から利用可能）：

#### 固定長型

```cpp theme={null}
// Encode: string -> binary. Write the encoded bytes via out.buffer() and
// out.set_length(n); call out.set_null() for SQL NULL, out.warning(msg) for
// recoverable bad input, or out.error(msg) to abort the statement. Returning
// without calling any of these surfaces a default warning.
using TypeEncodeFunc = void (*)(std::string_view from, vsql::CustomResult out);

// Decode: binary -> string. Report the outcome by calling
// out.set_length(n), out.set(sv), out.set_null(), out.warning(msg), or
// out.error(msg). If none is called the wrapper falls back to a default
// "failed to decode value" ERROR.
using TypeDecodeFunc = void (*)(vsql::CustomArg in, vsql::StringResult out);

// Compare: returns -1, 0, or 1 (used for ORDER BY and indexes).
using TypeCompareFunc = int (*)(vsql::CustomArg a, vsql::CustomArg b);

// Hash: returns hash code (used for hash joins).
using TypeHashFunc = size_t (*)(vsql::CustomArg in);
```

これらの演算を `vsql::make_type<kTypeName>()` を使用して登録します。型名は非型テンプレートパラメータ（NTTP）として渡されます — `static constexpr const char[]` 配列です。ビルダーはこの NTTP から `TYPE::method` 形式（例：`"MYTYPE::from_string"`）の VDF 名を自動生成するため、手動の文字列マッチングは不要です。ビルド済みの型オブジェクトを拡張機能ビルダーの `.type()` に渡します。型演算用の個別の `.func()` 呼び出しは必要ありません。

<Warning>
  型名は `static constexpr const char[]` 変数でなければなりません — 文字列リテラルを非型テンプレートパラメータとして使用することはできません。`"MYTYPE"` を直接渡すと、以下のようなコンパイラエラーが発生します：

  ```
  error: '"MYTYPE"' is not a valid template argument for type 'const char*'
  ```

  以下に示すように、名前付き配列として宣言してください。
</Warning>

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

using namespace vsql;

static constexpr const char kMyTypeName[] = "MYTYPE";

constexpr auto MYTYPE =
    vsql::make_type<kMyTypeName>()
        .persisted_length(8)
        .max_decode_buffer_length(64)
        .from_string<&my_encode>()   // auto: "MYTYPE::from_string"
        .to_string<&my_decode>()     // auto: "MYTYPE::to_string"
        .compare<&my_compare>()      // auto: "MYTYPE::compare"
        .hash<&my_hash>()            // optional, auto: "MYTYPE::hash"
        .intrinsic_default_str("0")  // string-literal intrinsic default
        .build();

VEF_GENERATE_ENTRY_POINTS(
    make_extension()
        .type(MYTYPE))
```

`from_string`, `to_string`, `compare` のいずれかが欠けている場合、`build()` はコンパイル時に失敗します。各テンプレートメソッドは `static_assert` を介して関数ポインタのシグネチャをチェックします。

#### 組み込みデフォルト

`IGNORE` モード（例：`INSERT IGNORE` または `UPDATE IGNORE`）で `NOT NULL` カスタム型列に `NULL` が受け取られると、サーバーはエラーを発生させるのではなくフォールバック値を生成するために組み込みデフォルトを呼び出します。組み込みデフォルトは文字列表現を提供し、サーバーは型の `from_string` 関数を使用してそれをバイナリに変換します。

<Note>
  `.intrinsic_default_str()` と `.intrinsic_default_vdf()` の両方を省略した場合、
  サーバーはフォールバックとして `from_string("")` を呼び出します。これは型が**初めて使用される**（テーブル作成時）に発生し、`INSTALL EXTENSION` 時ではありません。エンコード関数が空文字列を拒否する場合、または間違ったバイト数にエンコードする場合、型初期化は SQL クライアントで確認できるエラーで失敗します：

  ```
  Type 'MYTYPE' failed to initialize: from_string VDF encoded intrinsic
  default input '' to N bytes, expected persisted_length=M
  ```

  固定長型の場合、デフォルト文字列は正確に `persisted_length` バイトにエンコードされる必要があります。空文字列が有効な入力ではない型には明示的なデフォルトを設定してください。
</Note>

**文字列リテラル: `.intrinsic_default_str()`**

定数デフォルトの場合、型ビルダーに文字列を直接渡します（上記の固定長の例で `.intrinsic_default_str("0")` と示されている通り）。

**VDF ベース: `.intrinsic_default_vdf()` + `make_intrinsic_default`**

デフォルト値が型パラメータに依存する場合、これらのシグネチャのいずれかに基づいて関数を実装してください（`<villagesql/vsql.h>` から利用可能）：

<Warning>
  **破壊的変更**: `IntrinsicDefaultFunc` および
  `IntrinsicDefaultWithParamsFunc` は `const char*` の代わりに `std::string` を返します。
  既存の組み込みデフォルト実装を直接 `std::string` を返すように更新してください。
</Warning>

```cpp theme={null}
// Fixed (no type parameters):
using IntrinsicDefaultFunc = std::string (*)(char *error_msg);

// Parameterized (receives cached parsed params):
template <typename P>
using IntrinsicDefaultWithParamsFunc = std::string (*)(const P &,
                                                       char *error_msg);
```

デフォルト値の `std::string` 表現を返します。エラー時には `error_msg` にメッセージを書き込み、任意の値を返します（SDK は `error_msg[0] != '\0'` をチェックしてエラーを検出します）。`make_intrinsic_default<&fn>("vdf_name")`（引数 1 つ：VDF 名）で登録し、型ビルダーで `.intrinsic_default_vdf()` を使用してその名前を参照します。以下のパラメータ化された型の例に完全な登録パターンを示します。

```cpp theme={null}
std::string mytype_default(const MyTypeParams &p, char * /*error_msg*/) {
  return /* build string representation based on p */;
}
```

<h4 id="parameterized-types">
  パラメータ化された型
</h4>

可変長型は、エンコード、デコード、比較、ハッシュ時に列の宣言されたパラメータが必要で、割り当てサイズとレイアウトを決定します。パース関数と逆の `to_strings` 関数を持つ params 構造体を定義し、`.params<P, &ParseFunc, &ToStringsFunc>()` で型ビルダーに登録し、型演算関数の第 1 引数として `const P&` を使用します。SDK は一意のパラメータ組み合わせごとにパース結果をキャッシュするため、パース関数は型インスタンス化ごとに最大 1 回だけ実行されます。`to_strings` 関数は `parse` の逆であり、型付きの `P` を正規の key/value 文字列形式に戻して書き込むため、サーバーは `parse` が消費するのと同じ形状で推論された params を公開できます。

```cpp theme={null}
struct MyTypeParams {
  int64_t dimension;
  static MyTypeParams parse(const std::map<std::string, std::string> &p) {
    return {.dimension = stoll(p.at("dimension"))};
  }
  static void to_strings(const MyTypeParams &p,
                         std::map<std::string, std::string> &out) {
    out["dimension"] = std::to_string(p.dimension);
  }
};

void mytype_encode(vsql::MaybeParams<MyTypeParams> &params,
                   std::string_view from, vsql::CustomResult out) {
  const MyTypeParams &p = params.value();  // is_known() is always true at runtime
  size_t bytes = (size_t)p.dimension * 4;
  auto buf = out.buffer();
  if (buf.size() < bytes) { out.error("MYTYPE: buffer too small"); return; }
  // ... parse from, write to buf ...
  out.set_length(bytes);
}

void mytype_decode(vsql::CustomArgWith<MyTypeParams> in,
                   vsql::StringResult out) {
  const MyTypeParams &p = in.params();
  // ... read p.dimension floats from in.value(), write to out.buffer() ...
  out.set_length(bytes_written);
}

int mytype_compare(vsql::CustomArgWith<MyTypeParams> a,
                   vsql::CustomArgWith<MyTypeParams> b) {
  // Returns -1, 0, or 1.
}

size_t mytype_hash(vsql::CustomArgWith<MyTypeParams> in) {
  // Returns hash code.
}

// Converts MYTYPE(N) integer syntax to a parameter map.
// Signature: IntToTypeParamsFunc from <villagesql/vsql.h>.
bool mytype_int_to_params_fn(int64_t value,
                             std::map<std::string, std::string> &params,
                             char *error_msg) {
  if (value <= 0) {
    snprintf(error_msg, VEF_MAX_ERROR_LEN,
             "MYTYPE: dimension must be a positive integer");
    return true;
  }
  params["dimension"] = std::to_string(value);
  return false;  // success
}

// Validates parameters and computes storage sizes.
// Signature: ResolveTypeParamsFunc from <villagesql/vsql.h>.
bool mytype_resolve_params_fn(const std::map<std::string, std::string> &params,
                              vsql::ResolvedTypeParams *result,
                              char *error_msg) {
  int64_t dim = std::stoll(params.at("dimension"));
  result->persisted_length = dim * 4;
  result->max_decode_buffer_length = 64;
  return false;  // success
}
```

型ビルダーに `.params<>()` を登録します。`MYTYPE(N)` 整数構文を処理するには `.int_to_params<&mytype_int_to_params_fn>()` を、パラメータの検証とストレージサイズの計算には `.resolve_params<&mytype_resolve_params_fn>()` を使用します。すべての有効なパラメータ化にわたる永続化バイトサイズの上限制限を指定して `.max_persisted_length(N)` を呼び出します。サーバーはこれを型パラメータ推論パスでのみ使用し、このパスではまだ params が推論されていないため `resolve_params` を参照してエンコードバッファのサイズを設定できません。VDF ベースの組み込みデフォルトには、VDF 名と共に `.intrinsic_default_vdf()` を使用し、`make_intrinsic_default<&mytype_default>()` を介して VDF を個別に登録します。

```cpp theme={null}
static constexpr const char kMyTypeName[] = "MYTYPE";

// Maximum valid dimension for MYTYPE.
constexpr int64_t kMyTypeMaxDimension = 1024;  // your max valid dimension
// Upper bound on MYTYPE's persisted byte size across all valid params.
constexpr int64_t kMyTypeMaxPersistedLength = kMyTypeMaxDimension * 4;

constexpr auto MYTYPE =
    vsql::make_type<kMyTypeName>()
        .persisted_length(-1)
        .max_decode_buffer_length(16)
        .max_persisted_length(kMyTypeMaxPersistedLength)
        .params<MyTypeParams, &MyTypeParams::parse, &MyTypeParams::to_strings>()
        .int_to_params<&mytype_int_to_params_fn>()
        .resolve_params<&mytype_resolve_params_fn>()
        .from_string<&mytype_encode>()
        .to_string<&mytype_decode>()
        .compare<&mytype_compare>()
        .intrinsic_default_vdf("mytype_intrinsic_default")
        .build();

using namespace vsql;

VEF_GENERATE_ENTRY_POINTS(
    make_extension()
        .type(MYTYPE)
        .func(make_intrinsic_default<&mytype_default>(
            "mytype_intrinsic_default")))
```

パラメータ化されたバリアント — `TypeEncodeWithParamsFunc<P>`,
`TypeDecodeWithParamsFunc<P>`, `TypeCompareWithParamsFunc<P>`, および
`TypeHashWithParamsFunc<P>` — は `ParamsToStringsFunc<P>`
(`void fn(const P&, std::map<std::string,std::string>&)`) と共に `<villagesql/vsql.h>` から利用可能です。
`vsql::make_type` テンプレートメソッドは params 引数を検出し、自動的に params キャッシュを経由してルーティングします。エンコード関数は第 1 引数として `vsql::MaybeParams<P> &` を受け取ります。実行時には `is_known()` は常に true であり、`value()` は `const P&` を返します。デコード、比較、ハッシュのバリアントは `vsql::CustomArgWith<P>` を受け取り、その `params()` アクセサは `const P&` を返します。

#### ストアドプロシージャ内のカスタム型

カスタム拡張型はストアドプロシージャのパラメータ型や `DECLARE` 変数宣言で使用できます。サーバーはインストールされた拡張機能の型メタデータを使用して、ルーチン実行時にカスタム型を解決します。

```sql theme={null}
DELIMITER //
CREATE PROCEDURE insert_complex(IN val COMPLEX)
BEGIN
  DECLARE tmp COMPLEX;
  SET tmp = val;
  INSERT INTO t1 VALUES (tmp);
END //
DELIMITER ;
```

### 拡張機能システム変数

拡張機能システム変数はプレビュー機能です。完全な API リファレンス、ファクトリ関数、SQL アクセス、および完全な例については、[Preview Capabilities](/docs/ja/mysql-8.4/0.0.4/preview-capabilities#system-variables) を参照してください。

### 拡張機能ステータス変数

拡張機能ステータス変数はプレビュー機能です。完全な API リファレンス、ファクトリ関数、SQL アクセス、および完全な例については、[Preview Capabilities](/docs/ja/mysql-8.4/0.0.4/preview-capabilities#status-variables) を参照してください。

### キーリングアクセス

キーリングアクセスはプレビュー機能です。完全な API リファレンス、結果コード、および完全な例については、[Preview Capabilities](/docs/ja/mysql-8.4/0.0.4/preview-capabilities#keyring-access) を参照してください。

### 列ストレージ

列ストレージはプレビュー機能です。完全な API リファレンスおよび完全な例については、[Preview Capabilities](/docs/ja/mysql-8.4/0.0.4/preview-capabilities#column-storage) を参照してください。

### 拡張機能登録メタデータの検査

`INFORMATION_SCHEMA.EXTENSION_REGISTRATION` は、読み込まれた各拡張機能のメモリ内 VEF 登録構造体を JSON ドキュメントとして公開します。`INSTALL EXTENSION` 後にサーバーが拡張機能の関数、型、システム変数を正しくパースしたことを確認するために使用します。

```sql theme={null}
SELECT EXTENSION_NAME, NEGOTIATED_PROTOCOL, REGISTRATION_JSON
FROM INFORMATION_SCHEMA.EXTENSION_REGISTRATION
WHERE EXTENSION_NAME = 'my_ext';
```

| Column                | Type              | Description                                                          |
| --------------------- | ----------------- | -------------------------------------------------------------------- |
| `EXTENSION_NAME`      | `VARCHAR(64)`     | インストールされた拡張機能の名前。                                                    |
| `NEGOTIATED_PROTOCOL` | `BIGINT UNSIGNED` | 拡張機能とサーバー間でネゴシエートされた VEF プロトコルバージョン。                                 |
| `REGISTRATION_JSON`   | `TEXT`            | `funcs` および `types` 配列を含む `vef_registration_t` 構造体の JSON シリアライゼーション。 |

<h2 id="running-regression-tests">
  回帰テストの実行
</h2>

VillageSQL のビルドディレクトリから MySQL Test Runner を使用して拡張機能の回帰テストを実行します。

### フルスイートの実行

拡張機能のすべてのテストを実行するには：

```bash theme={null}
cd $BUILD_HOME
./mysql-test/mysql-test-run.pl --suite=/path/to/your/extension/test --parallel=auto
```

### 個別テストの実行

単一のテストケースを実行するには、スイートパスとテスト名を指定します：

```bash theme={null}
cd $BUILD_HOME
./mysql-test/mysql-test-run.pl --suite=/path/to/your/extension/test my_test_name
```

## 新しいテストの作成

新機能の追加やバグ修正を行う際は、対応する回帰テストを追加する必要があります。

### テストの場所

拡張機能のテストは VillageSQL サーバーの `mysql-test/suite/` ツリーではなく、拡張機能自身のリポジトリ内の `test/` ディレクトリに配置されます。

* テストファイルは `.test` で終わり、`test/t/` に配置されます。
* 期待される結果ファイルは `.result` で終わり、`test/r/` に配置されます。

例えば、`my_extension` という名前の拡張機能の場合：

* `test/t/my_new_test.test`
* `test/r/my_new_test.result`

### テストファイルの規約

典型的な拡張機能テストは、拡張機能をインストールし、SQL を実行し、アンインストールします：

```sql theme={null}
# Description of the test

INSTALL EXTENSION my_extension;

# ... Your Test Code Here ...
CREATE TABLE t1 (val MYTYPE);
INSERT INTO t1 VALUES ('some_value');
SELECT * FROM t1;
DROP TABLE t1;

UNINSTALL EXTENSION my_extension;
```

テスト出力にテストランナーの一時ディレクトリのパスが含まれる場合、`.test` ファイル内にこのディレクティブを追加して正規化してください — これがないと、記録された結果には他のマシンで壊れる絶対パスが含まれます：

```sql theme={null}
--replace_result $MYSQLTEST_VARDIR MYSQLTEST_VARDIR
```

### テストの追加手順

1. 拡張機能の `test/t/` ディレクトリに **`.test` ファイルを作成する**。
2. 拡張機能の `test/r/` ディレクトリに **空の `.result` ファイルを作成する**。
3. **`--record` をつけてテストを実行**し、期待される出力を生成します：
   ```bash theme={null}
   cd $BUILD_HOME
   ./mysql-test/mysql-test-run.pl --suite=/path/to/your/extension/test --record my_new_test
   ```
4. 生成された `.result` ファイルで **出力を検証**し、期待どおりであることを確認します。

## テストのデバッグ

テストが失敗した場合、テストフレームワークは詳細なログを提供します。

* **テスト出力:** `mysql-test/var/log/mysqltest.log`（結合）または `mysql-test/var/log/<test_name>/`（テスト別ディレクトリ）を確認します。
* **サーバーエラーログ:** `mysql-test/var/log/mysqld.1.err` を確認します。VillageSQL 固有のログメッセージ（`LogVSQL()` を介して出力）は、サーバーが `--log-error-verbosity=3` で実行されている場合のみ表示されます。
* **差分:** フレームワークは実際の出力と期待される `.result` ファイルの間の差分を出力します。

追加のデバッグ情報をつけてテストを実行するには：

```bash theme={null}
cd $BUILD_HOME
./mysql-test/mysql-test-run.pl --verbose --suite=/path/to/your/extension/test my_new_test

# To surface LogVSQL() messages in the error log:
./mysql-test/mysql-test-run.pl --mysqld=--log-error-verbosity=3 \
    --suite=/path/to/your/extension/test my_new_test
```

## 関連資料

* [Creating Extensions](/docs/ja/mysql-8.4/0.0.4/create) — エンドツーエンドのビルド手順、CMake セットアップ、およびインストール
* [Extension API Reference](/docs/ja/mysql-8.4/0.0.4/extension-api-reference) — VDF 契約、null 処理、およびバッファサイズ設定
* [Extension Architecture](/docs/ja/mysql-8.4/0.0.4/architecture) — ライフサイクル、Victionary キャッシュ、パフォーマンスパターン、およびセキュリティモデル
