拡張機能関数の作成
拡張機能関数は C++ で記述され、VEF に登録されます。VDF、型、および拡張機能ビルダーの API にアクセスするには、単一のヘッダーをインクルードしてください:例外安全性
例外の封じ込めは拡張機能の責任です。SDK は実装をtry/catch でラップせず、サーバーもラップしません。そのため、エントリポイントから抜け出した例外は、上位のどこにもハンドラーがないまま C ABI 境界を越えてスタックを巻き戻し、サーバープロセスが終了します。通常の SQL 入力でもこれに到達します: std::stoi() は数値以外の文字列でスローし、nlohmann::json::dump() は有効な UTF-8 ではないテキストでスローします。
登録するすべてのエントリポイントの本体をラップしてください: VDF、集計関数、prerun および postrun フック、およびカスタム型の演算です。
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 を参照)。
結果型: 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 つの整数を加算:
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 は行を折りたたみ、結果関数は最終状態を読み取ります。
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など)は結果関数のシグネチャから推論されます。
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 を参照してください。
ステートメントごとの状態(Prerun と Postrun)
一部の VDF では、単一のクエリがアクセスするすべての行にわたる状態が必要です — 呼び出しカウンター、キャッシュされた結果、オープンリソースなど。prerun フックで割り当て、VDF 本体からアクセスし、postrun フックで解放します。両方のフックはステートメントごとに 1 回実行され、VDF 本体は行ごとに 1 回実行されます。.prerun<&Hook>() と .postrun<&Hook>() で登録します。必要なシグネチャは次の通りです:
状態を保存するには
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) を呼び出します。
可変長引数 VDF
可変長引数 VDF は、任意の SQL 型の任意の数の引数を受け入れます。func ビルダーで.varargs() を宣言し、これは .no_params() および .param(TYPE) と排他です。本体は通常の固定引数型の代わりに vsql::VarArgs 引数を受け取ります。
フレームワークは可変長引数 VDF の引数カウントや型を検証できません。すべての可変長引数登録には、不正な入力時に PrerunResult::error() を呼び出すか、結果バッファのサイズ設定に PrerunResult::request_buffer_size(n) を呼び出す prerun フックをペアにしてください。
範囲 for ループで引数を反復処理します。各 AnyArg 要素は値を読み取る前に型チェックが必要です:
いずれのアクセサを呼び出す前にも
is_null() をチェックしてください — 4 つすべてで null 引数に対する動作は未定義です。
PrerunArgType::custom_type() は、引数のカスタム型名を std::string_view として返します。カスタム型でない引数に対しては空になります。is_custom() だけでは、サーバーに登録されているすべてのカスタム型を受け入れます。custom_type() の比較と組み合わせることで、可変長引数の呼び出しを特定の 1 つの型に絞り込めます。
解決時に関数の型パラメータを決定する
サーバーの組み込みルールは通常、引数の宣言された型から戻り型のパラメータを導き出します。それができない場合もあります。たとえば、tvector_concat(TVECTOR(M), TVECTOR(N)) は TVECTOR(M+N) を返します。組み込みルールは次元が異なる 2 つの TVECTOR 引数を拒否し、引数のパラメータを戻り型にコピーすることはできても、それらを加算することはできません。このような場合には、func ビルダーの .bind_and_check_types<&Hook>() で、次のシグネチャを持つ bind-and-check-types フック を登録します:
.bind_and_check_types() と .varargs() は同じ関数上で排他です — 可変長引数 VDF には、フックが判断の対象とする固定の引数リストがありません。ビルダーチェーンで .bind_and_check_types<&Hook>() より前に .varargs() を宣言すると、コンパイル時の static_assert の失敗になります。逆の順序で宣言するとコンパイルは通りますが、サーバーがロードするときに拡張機能は拒否されます: 登録はすべての関数の最終的なビルド済み状態を走査し、可変長引数であり、かつバインドフックを持つ最初の関数を、次のエラーで拒否します:
vsql_tvector は、tvector_concat にこのようなフックを登録しています:
vsql_tvector が含まれますが、リリースの tarball には含まれません。
拡張機能のロードフックとアンロードフック
拡張機能には、呼び出しごとではなくロードごとに 1 回だけ実行する作業が必要になることがあります — CPU 固有の実装の選択、自身の設定を一度だけ読み取ること、VDF が後で読み取るルックアップテーブルの構築などです。その作業は拡張機能ビルダーの.on_init<&Fn>() で登録し、対応する後処理は .on_deinit<&Fn>() で登録します。どちらもシグネチャが void() の関数を取り、一緒に登録する必要があります — 一方だけを宣言するとコンパイルに失敗します。何らかの他の方法で、対になっていない状態でサーバーに到達した拡張機能は、代わりにロード時に拒否されます:
on_init はロードごとに 1 回実行されます — INSTALL EXTENSION 時と、サーバー起動のたびに — サーバーが拡張機能を検証し、必要な機能を設定した後に実行されるため、登録済みのシステム変数はすでに設定された値を保持しています。特に起動時には、on_init は永続化されたシステム変数の値が適用されるまで遅延されるため、SET PERSIST で設定された値の代わりにコンパイル時のデフォルトを見ることは決してありません。サーバーが拒否した拡張機能に対しては決して実行されません。on_deinit はアンロード時 — UNINSTALL EXTENSION またはサーバーシャットダウン時 — に、サーバーが同じ機能の設定を解除する前に実行されるため、on_init と同じライブ状態を見ます。
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 を使用します。
カスタム型演算
型演算ビルダーの完全なリファレンス — エンコード、デコード、比較、ハッシュ、組み込みデフォルト、およびパラメータ化された型 — については、Type Operations を参照してください。プレビュー機能
以下の VEF 機能は、オプトインのプレビューヘッダーとして利用可能です。ABI と API はまだ活発に開発中です。完全なリファレンスについては、Preview Capabilities を参照してください。- 拡張機能システム変数 — Preview Capabilities → System Variables
- 拡張機能ステータス変数 — Preview Capabilities → Status Variables
- キーリングアクセス — Preview Capabilities → Keyring Access
- 列ストレージ — Preview Capabilities → Column Storage
拡張機能登録メタデータの検査
INFORMATION_SCHEMA.EXTENSION_REGISTRATION は、読み込まれた各拡張機能のメモリ内 VEF 登録構造体を JSON ドキュメントとして公開します。INSTALL EXTENSION 後にサーバーが拡張機能の関数と型を正しくパースしたことを確認するために使用します。
関連資料
- Creating Extensions in C++ — エンドツーエンドのビルド手順、CMake セットアップ、およびインストール
- C++ Testing — ローカル開発サーバー、MTR、および失敗のデバッグ
- Type Operations — エンコード、デコード、比較、ハッシュ、パラメータ化された型
- C++ API Reference — VDF 契約、null 処理、およびバッファサイズ設定
- Extension Architecture — ライフサイクル、Victionary キャッシュ、パフォーマンスパターン、およびセキュリティモデル

