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

# C++ 開発

> C++ 拡張機能向けの VDF 作成の詳細 — 引数と結果の型、例外安全性、集計、prerun/postrun、可変長引数、および登録。

このガイドは、C++ VDF 実装を作成するための詳細なリファレンスです。エンドツーエンドのビルド手順をカバーする [Creating Extensions in C++](https://villagesql.com/docs/ja/extensions/build/create.md) と、テストと反復のループをカバーする [C++ Testing](https://villagesql.com/docs/ja/sdk/cpp/testing.md) の補完資料です。

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

## 拡張機能関数の作成

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

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

## 例外安全性

例外の封じ込めは拡張機能の責任です。SDK は実装を `try`/`catch` でラップせず、サーバーもラップしません。そのため、エントリポイントから抜け出した例外は、上位のどこにもハンドラーがないまま C ABI 境界を越えてスタックを巻き戻し、サーバープロセスが終了します。通常の SQL 入力でもこれに到達します: `std::stoi()` は数値以外の文字列でスローし、`nlohmann::json::dump()` は有効な UTF-8 ではないテキストでスローします。

> [!NOTE]
>
> **0.0.8-dev での変更点。** dev ABI（Protocol 4、`-DVSQL_USE_DEV_ABI=ON`）では、SDK は VDF 本体、または集計関数の accumulate 関数や result 関数から抜け出した例外を捕捉します。サーバーが終了する代わりに、ステートメントは VDF エラーで失敗します。安定版 ABI は変更されません。

登録するすべてのエントリポイントの本体をラップしてください: VDF、集計関数、prerun および postrun フック、およびカスタム型の演算です。

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

using namespace vsql;

void to_int_impl(StringArg in, IntResult out) {
  if (in.is_null()) { out.set_null(); return; }
  try {
    out.set(std::stoi(std::string(in.value())));  // throws on bad input
  } catch (const std::exception &e) {
    out.warning(e.what());
  } catch (...) {
    out.warning("unexpected error");
  }
}
```

`const std::exception &` に加えて `...` もキャッチしてください: 依存ライブラリが `std::exception` を継承しない型をスローする場合があり、それもサーバーを終了させます。`warning()` と `error()` の選択は、他の失敗の場合と同じ基準で行ってください — ステートメントが継続すべき不正な入力には `warning()`、続行が安全でない場合には `error()` です。

## 引数と結果の型

VDF のパラメータと結果は、型安全な引数型と結果型として渡されます。フレームワークは関数シグネチャからそれらを検出し、自動的に適応します — `make_func` の登録構文は変更されません。

**引数型:** `IntArg`, `RealArg`, `StringArg`, `CustomArg` — 各型は `is_null()` と `value()` を提供します。パラメータ化されたカスタム型の場合、`CustomArgWith<P>` はキャッシュされた解析済みの params 構造体を返す `params()` アクセサを追加します（[Parameterized Types](https://villagesql.com/docs/ja/sdk/cpp/type-operations.md#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 警告を追加します。その NULL はサーバーから見れば通常の値であるため、他の NULL と同じ形でのみステートメントエラーになります: `NOT NULL` カラムへ書き込むと、`INSERT INTO t VALUES (NULL)` と同様に `STRICT_TRANS_TABLES` の下で失敗します。NULL 可能なカラムへの書き込みは、警告付きで成功します。エンコード関数で解析できない文字列など、回復可能な不正な入力には `warning(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(MYTYPE)`）の場合、サーバーは結果バッファのサイズを解決された戻り型の `persisted_length` に自動的に合わせます — 拡張機能作者はこの場合、関数ビルダーで `.buffer_size(...)` を宣言する必要はありません。`prerun` がバッファをさらに拡張する場合、その大きなサイズは保持されます。これにより、例えば `SVECTOR::from_string('[…1024 floats…]')` が結果バッファの領域不足なしでワイドベクトルをエンコードできます。

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

## 集計 VDF

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

結果関数は `void(const State&, ResultType)` のシグネチャを持つ必要があり、`ResultType` は `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&)` のリセット関数を登録します。
* `.accumulate<&fn>()` は `void(State&, TypedArgs...)` の折りたたみ関数を登録します。`TypedArgs` は関数シグネチャ（`IntArg`, `StringArg` など）から推論されます。`.accumulate<>()` はすべての `.param(TYPE)` 呼び出しの後に呼び出してください: ビルダーは型付き引数の数を宣言された SQL パラメータ数とコンパイル時に照合するため、それより前に呼び出すとビルドに失敗します。
* 結果型（`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); }
```

`StringResult` の集計 VDF はテキストを返します。結果は `utf8mb4_bin` の文字セットと照合順序を報告するため、クライアントは 16 進数ではなく文字として表示します — スカラー VDF の STRING パスと同じです。また、`.max_result_length(n)`（オプトイン形式の dev ABI ヘッダー（`-DVSQL_USE_DEV_ABI=ON`）と Protocol 4 が必要）も同様に適用し、マテリアライズされた集計結果（`GROUP BY`/`DISTINCT` の一時テーブル、`CREATE TABLE ... SELECT`、または UNION）のサイズを設定して引数幅で切り捨てられないようにします。サイズ設定のルールと上限については [Custom Buffer Sizes](https://villagesql.com/docs/ja/extensions/build/create.md#custom-buffer-sizes) を参照してください。

## ステートメントごとの状態（Prerun と Postrun）

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

> [!NOTE]
>
> **0.0.8-dev での変更点。** prerun が成功していれば、ステートメントが最初の行より前に失敗した場合にも postrun が実行されます。

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

| フック | 必要なシグネチャ |
| - | - |
| Prerun | `void(vsql::PrerunArgs, vsql::PrerunResult)` |
| Postrun | `void(vsql::PostrunArgs)` |

状態を保存するには `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 が必要です。古いサーバーはインストール時に拡張機能を拒否します。

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

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

| 述語 | アクセサ | 戻り値の型 |
| - | - | - |
| `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 引数に対する動作は未定義です。

`PrerunArgType::custom_type()` は、引数のカスタム型名を `std::string_view` として返します。カスタム型でない引数に対しては空になります。`is_custom()` だけでは、サーバーに登録されているすべてのカスタム型を受け入れます。`custom_type()` の比較と組み合わせることで、可変長引数の呼び出しを特定の 1 つの型に絞り込めます。

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

// POINT2D stores two little-endian int32 (x, y) in 8 bytes — registration
// omitted here; see Custom Types for make_type<>().

void point_path_prerun(PrerunArgs args, PrerunResult out) {
  if (args.size() == 0) {
    out.error("point_path requires at least one point");
    return;
  }
  for (size_t i = 0; i < args.size(); i++) {
    auto t = args.type_at(i);
    if (!t.is_custom() || t.custom_type() != "POINT2D") {
      out.error("point_path: every argument must be a POINT2D");
      return;
    }
  }
  out.request_buffer_size(16 + args.size() * 32);
}

void point_path(VarArgs args, StringResult out) {
  auto buf = out.buffer();
  size_t off = 0;
  bool first = true;
  for (auto a : args) {
    if (a.is_null()) { out.set_null(); return; }
    auto bytes = a.as_custom();
    int x, y;
    memcpy(&x, bytes.data(), 4);
    memcpy(&y, bytes.data() + 4, 4);
    if (!first) {
      memcpy(buf.data() + off, " -> ", 4);
      off += 4;
    }
    first = false;
    off += static_cast<size_t>(
        snprintf(buf.data() + off, buf.size() - off, "(%d,%d)", x, y));
  }
  out.set_length(off);
}

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

```sql theme={null}
SELECT point_path(POINT2D::from_string('0,0'), POINT2D::from_string('1,2'));
-- (0,0) -> (1,2)
SELECT point_path('1,2');
-- ERROR 1123 (HY000): Can't initialize function 'point_path'; point_path: every argument must be a POINT2D
```

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

constexpr size_t kBytearrayLen = 8;

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();
    if (off + bytes.size() > dst.size()) { out.error("ba_concat_all: result too long"); return; }
    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()
```

## 解決時に関数の型パラメータを決定する

サーバーの組み込みルールは通常、引数の宣言された型から戻り型のパラメータを導き出します。それができない場合もあります。たとえば、`tvector_concat(TVECTOR(M), TVECTOR(N))` は `TVECTOR(M+N)` を返します。組み込みルールは次元が異なる 2 つの `TVECTOR` 引数を拒否し、引数のパラメータを戻り型にコピーすることはできても、それらを加算することはできません。このような場合には、func ビルダーの `.bind_and_check_types<&Hook>()` で、次のシグネチャを持つ **bind-and-check-types フック** を登録します：

```cpp theme={null}
void my_hook(vsql::BindArgs args, vsql::BindResult out);
```

`.bind_and_check_types()` と `.varargs()` は同じ関数上で排他です — 可変長引数 VDF には、フックが判断の対象とする固定の引数リストがありません。ビルダーチェーンで `.bind_and_check_types<&Hook>()` より前に `.varargs()` を宣言すると、コンパイル時の `static_assert` の失敗になります。逆の順序で宣言するとコンパイルは通りますが、サーバーがロードするときに拡張機能は拒否されます: 登録はすべての関数の最終的なビルド済み状態を走査し、可変長引数であり、かつバインドフックを持つ最初の関数を、次のエラーで拒否します：

```text theme={null}
ERROR 3219 (HY000): Failed to load VEF extension '<name>': vef_register returned an error: VDF '<function>' declares both .varargs() and .bind_and_check_types(). This is not currently supported.
```

> [!WARNING]
>
> `.bind_and_check_types<&Hook>()` と `<villagesql/vsql/bind_check_types.h>` は、オプトイン形式の dev ABI ヘッダー（`-DVSQL_USE_DEV_ABI=ON`）でのみ宣言されており、VEF Protocol 4 が必要です。デフォルトの安定版 ABI ビルドにはどちらもないため、フックを登録するとコンパイルに失敗します。

サーバーは、関数の引数に対して行う 2 つのパスの間にこのフックを呼び出します: 最初のパスは各引数の基本型を検査し、次にフックが実行され、2 番目のパスはフックが答えを返した各引数にその答えを適用します。フックがインストールされている場合、最初のパスは同じカスタム型の引数間で共有パラメータを収集しないため、引数同士が一致している必要はありません。素の文字列リテラルのように、自身の型がまだ解決されていない引数は、フックからのみパラメータを得ます。パラメータ化された戻り型のパラメータも、引数からコピーする組み込みルールの代わりに、フックから得られます。

サーバーのソースツリーにあるサンプル拡張機能 [`vsql_tvector`](https://github.com/villagesql/villagesql-server/blob/main/villagesql/examples/vsql-tvector/src/tvector.cc) は、`tvector_concat` にこのようなフックを登録しています：

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

// Excerpt from villagesql/examples/vsql-tvector/src/tvector.cc. That file
// also has the standard library includes, the constants kDefaultBytesPerElem
// (4) and kTVectorMaxDimension (4096), the TVECTOR type, the tvector_concat
// function body, and the helpers common_element_width() and
// bind_params_for_vector(), which read an argument's resolved params or count
// the elements of a constant literal.

struct TVectorParams {
  int64_t dimension;
  size_t bytes_per_elem;  // 4 for float, 8 for double

  static TVectorParams parse(const std::map<std::string, std::string> &params) {
    auto dim_it = params.find("dimension");
    int64_t dim = strtoll(dim_it->second.c_str(), nullptr, 10);
    size_t bytes = kDefaultBytesPerElem;
    auto type_it = params.find("type");
    if (type_it != params.end() && type_it->second == "double") bytes = 8;
    return TVectorParams{.dimension = dim, .bytes_per_elem = bytes};
  }

  // Inverse of parse: render a typed TVectorParams back into the canonical
  // key/value string form. Used by the SDK when the server needs to publish
  // inferred params (e.g., from a constant-string from_string) back in the
  // same shape that parse() consumes.
  static void to_strings(const TVectorParams &p,
                         std::map<std::string, std::string> &out) {
    out["dimension"] = std::to_string(p.dimension);
    out["type"] = (p.bytes_per_elem == 8) ? "double" : "float";
  }
};

// bind_and_check_types for tvector_concat.
//
// Two jobs the built-in rules cannot do:
//   1. permit arguments whose dimensions differ (parameter sharing rejects it);
//   2. compute the return dimension as M+N (the server return rule only
//      copies).
//
// A third job appears when an argument is a constant: a bare '[1,2,3]' has no
// type context, and with a hook installed nothing donates params to it, so the
// hook counts the elements itself and answers with set_arg(). The element type
// comes from whichever side the server already resolved, which is what the
// built-in rules would have donated.
void tvector_concat_bind(vsql::BindArgs args, vsql::BindResult out) {
  if (args.size() != 2) {
    out.error("expects 2 arguments");
    return;
  }

  std::string error_msg;
  size_t bpe = 0;
  if (common_element_width(args, bpe, error_msg)) {
    out.error(error_msg);
    return;
  }

  TVectorParams first{}, second{};
  bool publish_first = false, publish_second = false;

  if (bind_params_for_vector(args, 0, bpe, first, publish_first, error_msg) ||
      bind_params_for_vector(args, 1, bpe, second, publish_second, error_msg)) {
    out.error(error_msg);
    return;
  }

  // Hand back the params of whichever argument the server could not resolve.
  if (publish_first) out.set_arg(0, first);
  if (publish_second) out.set_arg(1, second);

  const int64_t total = first.dimension + second.dimension;
  if (total > kTVectorMaxDimension) {
    out.error("combined dimension " + std::to_string(total) +
              " exceeds the maximum of " +
              std::to_string(kTVectorMaxDimension));
    return;
  }
  out.set_return(TVectorParams{.dimension = total, .bytes_per_elem = bpe});
}

// Registration, inside make_extension():
// .func(make_func<&tvector_concat>("tvector_concat")
//     .returns(TVECTOR)
//     .param(TVECTOR)
//     .param(TVECTOR)
//     .bind_and_check_types<&tvector_concat_bind>()
//     .deterministic()
//     .build())
```

このフックにより、次元の異なる 2 つのベクトルを結合でき、定数リテラルの要素数も数えます。サーバーのソースビルドには `vsql_tvector` が含まれますが、リリースの tarball には含まれません。

```sql theme={null}
INSTALL EXTENSION vsql_tvector;
CREATE TABLE v2 (v vsql_tvector.TVECTOR(2));
CREATE TABLE v3 (v vsql_tvector.TVECTOR(3));
INSERT INTO v2 VALUES ('[1,2]');
INSERT INTO v3 VALUES ('[3,4,5]');

SELECT vsql_tvector.tvector_concat(v2.v, v3.v) AS c,
       vsql_tvector.tvector_dim(vsql_tvector.tvector_concat(v2.v, v3.v)) AS dim
FROM v2, v3;
```

```text theme={null}
+-------------+------+
| c           | dim  |
+-------------+------+
| [1,2,3,4,5] |    5 |
+-------------+------+
```

```sql theme={null}
SELECT vsql_tvector.tvector_concat(v, '[6,7]') AS c FROM v3;
```

```text theme={null}
+-------------+
| c           |
+-------------+
| [3,4,5,6,7] |
+-------------+
```

## 拡張機能のロードフックとアンロードフック

拡張機能には、呼び出しごとではなくロードごとに 1 回だけ実行する作業が必要になることがあります — CPU 固有の実装の選択、自身の設定を一度だけ読み取ること、VDF が後で読み取るルックアップテーブルの構築などです。その作業は拡張機能ビルダーの `.on_init<&Fn>()` で登録し、対応する後処理は `.on_deinit<&Fn>()` で登録します。どちらもシグネチャが `void()` の関数を取り、一緒に登録する必要があります — 一方だけを宣言するとコンパイルに失敗します。何らかの他の方法で、対になっていない状態でサーバーに到達した拡張機能は、代わりにロード時に拒否されます：

```text theme={null}
ERROR 3219 (HY000): Failed to load VEF extension '<name>': extension registers an on_init hook without an on_deinit
```

> [!WARNING]
>
> `.on_init<&Fn>()` と `.on_deinit<&Fn>()` は、オプトイン形式の dev ABI ヘッダー（`-DVSQL_USE_DEV_ABI=ON`）でのみ宣言されています。デフォルトの安定版 ABI ビルドにはどちらのメソッドもないため、フックを登録するとコンパイルに失敗します。

`on_init` はロードごとに 1 回実行されます — `INSTALL EXTENSION` 時と、サーバー起動のたびに — サーバーが拡張機能を検証し、必要な機能を設定した後に実行されるため、登録済みのシステム変数はすでに設定された値を保持しています。特に起動時には、`on_init` は永続化されたシステム変数の値が適用されるまで遅延されるため、`SET PERSIST` で設定された値の代わりにコンパイル時のデフォルトを見ることは決してありません。サーバーが拒否した拡張機能に対しては決して実行されません。`on_deinit` はアンロード時 — `UNINSTALL EXTENSION` またはサーバーシャットダウン時 — に、サーバーが同じ機能の設定を解除する前に実行されるため、`on_init` と同じライブ状態を見ます。

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

static float *g_sqrt_table = nullptr;

void build_table() {
  g_sqrt_table = new float[256];
  for (int i = 0; i < 256; i++) g_sqrt_table[i] = std::sqrt(float(i));
}

void free_table() {
  delete[] g_sqrt_table;
  g_sqrt_table = nullptr;
}

void fast_sqrt(IntArg n, RealResult out) {
  if (n.is_null() || n.value() < 0 || n.value() > 255) {
    out.set_null();
    return;
  }
  out.set(g_sqrt_table[n.value()]);
}

VEF_GENERATE_ENTRY_POINTS(
    make_extension()
        .on_init<&build_table>()
        .on_deinit<&free_table>()
        .func(make_func<&fast_sqrt>("fast_sqrt")
                  .returns(REAL)
                  .param(INT)
                  .build()))
```

`INSTALL EXTENSION my_extension` の後、`SELECT fast_sqrt(16)` は `4` を返します — これは `build_table` がすでに実行されている場合にのみ得られる結果です。

## 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).no_params().build()))

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

## カスタム型演算

型演算ビルダーの完全なリファレンス — エンコード、デコード、比較、ハッシュ、組み込みデフォルト、およびパラメータ化された型 — については、[Type Operations](https://villagesql.com/docs/ja/sdk/cpp/type-operations.md) を参照してください。

## プレビュー機能

以下の VEF 機能は、オプトインのプレビューヘッダーとして利用可能です。ABI と API はまだ活発に開発中です。完全なリファレンスについては、[Preview Capabilities](https://villagesql.com/docs/ja/extensions/build/preview-capabilities.md) を参照してください。

* **拡張機能システム変数** — [Preview Capabilities → System Variables](https://villagesql.com/docs/ja/extensions/build/preview-capabilities.md#system-variables)
* **拡張機能ステータス変数** — [Preview Capabilities → Status Variables](https://villagesql.com/docs/ja/extensions/build/preview-capabilities.md#status-variables)
* **キーリングアクセス** — [Preview Capabilities → Keyring Access](https://villagesql.com/docs/ja/extensions/build/preview-capabilities.md#keyring-access)
* **列ストレージ** — [Preview Capabilities → Column Storage](https://villagesql.com/docs/ja/extensions/build/preview-capabilities.md#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';
```

| 列 | 型 | 説明 |
| - | - | - |
| `EXTENSION_NAME` | `VARCHAR(64)` | インストールされた拡張機能の名前。 |
| `NEGOTIATED_PROTOCOL` | `BIGINT UNSIGNED` | 拡張機能とサーバー間でネゴシエートされた VEF プロトコルバージョン。 |
| `REGISTRATION_JSON` | `VARCHAR(65535)` | `funcs` および `types` 配列と、拡張機能がビルドされた SDK バージョンを報告する `sdk_version` キーを含む `vef_registration_t` 構造体の JSON シリアライゼーション。 |

## 関連資料

* [Creating Extensions in C++](https://villagesql.com/docs/ja/extensions/build/create.md) — エンドツーエンドのビルド手順、CMake セットアップ、およびインストール
* [C++ Testing](https://villagesql.com/docs/ja/sdk/cpp/testing.md) — ローカル開発サーバー、MTR、および失敗のデバッグ
* [Type Operations](https://villagesql.com/docs/ja/sdk/cpp/type-operations.md) — エンコード、デコード、比較、ハッシュ、パラメータ化された型
* [C++ API Reference](https://villagesql.com/docs/ja/sdk/cpp/extension-api-reference.md) — VDF 契約、null 処理、およびバッファサイズ設定
* [Extension Architecture](https://villagesql.com/docs/ja/extensions/build/architecture.md) — ライフサイクル、Victionary キャッシュ、パフォーマンスパターン、およびセキュリティモデル


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.