Skip to main content
このガイドでは、VillageSQL 拡張機能の VDF 実装の作成と回帰テストの実行について解説します。これは、エンドツーエンドのビルド手順をカバーする Creating Extensions の補完資料です。
VEF Protocol 3 は v0.0.4 で安定版となりました。Protocol 4 は開発中で、オプトイン形式の dev ABI ヘッダー(-DVSQL_USE_DEV_ABI=ON)経由でのみ利用可能です。古い Protocol 2 に対してビルドされた拡張機能はサーバーによって拒否され、再ビルドが必要です。
VillageSQL サーバー自体に貢献する場合(拡張機能のビルドではない場合)は、mysql-test-run.pl を直接使用してテストを実行するなど、サーバー開発者のワークフロー全体をカバーする Build from Source を参照してください。

環境のセットアップ

拡張機能の開発とテストには、ビルド済みの VillageSQL サーバーが必要です。サーバーバイナリをコンパイルするには、Clone and Build from Source ガイドに従ってください。 ビルドが完了したら、villagesql CLI を使用してローカルの開発サーバーインスタンスを管理します。すべてのコマンドは VillageSQL がインストールされたディレクトリから実行してください。

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

サーバーインスタンスを初期化して起動します:
初期化時に root パスワードを設定するには:
複数の独立したインスタンスを管理するには、コマンドの前に --dir <path> を渡すか、--here を使用して現在の作業ディレクトリにサーバーディレクトリを作成します:

拡張機能ファイルの管理

SQL を介して拡張機能をインストールする前に、その .veb ファイルがサーバー上に存在している必要があります。CLI はサーバーの lib/veb/ ディレクトリを管理します:
init の前に lib/veb/ に配置された .veb ファイルは自動的にシードされます。ファイルを追加した後、SQL を介して拡張機能をインストールします:

拡張機能関数の作成

拡張機能関数は C++ で記述され、VEF に登録されます。SDK に完全にアクセスするには、単一のヘッダーをインクルードしてください:
型付きラッパーは、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 警告を追加します。厳格モード(STRICT_TRANS_TABLES)では、MySQL はこれを INSERT/UPDATE 時のステートメントエラーに昇格させるため、厳格なコンテキストでは error(msg) と同様に動作します。エンコード関数で解析できない文字列など、回復可能な不正な入力に使用してください。破損した保存データや、続行が安全でないあらゆる条件には error(msg) を使用してください。両方のメッセージは必要に応じてサーバーの内部エラーバッファに収まるように切り捨てられます。 スカラーの例 — 2 つの整数を加算:
バイナリの例 — カスタム型バッファをインプレースで変換:
StringResultCustomResult の場合、buffer() に書き込み、その後書き込んだバイト数で set_length() を呼び出します。buffer().size() が最大容量です。 カスタム型を返す VDF(returns(CUSTOM(MYTYPE)))の場合、サーバーは結果バッファのサイズを解決された戻り型の persisted_length に自動的に合わせます — 拡張機能作者はこの場合、関数ビルダーで .buffer_size(...) を宣言する必要はありません。prerun がバッファをさらに拡張する場合、その大きなサイズは保持されます。これにより、例えば SVECTOR::from_string('[…1024 floats…]') が結果ラッパーの領域不足なしでワイドベクトルをエンコードできます。 同じ拡張機能内の関数間で異なるスタイルを使用できます — 各関数のスタイルはそれぞれのシグネチャによって決定されます。

集計 VDF

集計 VDF は各 GROUP BY グループ内の行にわたって状態を蓄積し、SQL の SUMCOUNT のようにグループごとに 1 つの結果を返します。登録には make_aggregate_func<State, &result_fn>("name") を使用します。State 型はグループごとの蓄積バッファであり、prerunpostrun はそれを割り当てて削除するために自動生成されます。 結果関数は void(const State&, ResultWrapper) のシグネチャを持つ必要があり、ResultWrapperIntResult, 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>()prerunpostrun を自動生成します(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 を返さないカウンターの場合、プレーンな状態型を使用します:

ステートメントごとの状態(Prerun と Postrun)

一部の VDF では、単一のクエリが接触するすべての行にわたる状態が必要です — 呼び出しカウンター、キャッシュされた結果、オープンリソースなど。prerun フックで割り当て、VDF 本体からアクセスし、postrun フックで解放します。両方のフックはステートメントごとに 1 回実行され、VDF 本体は行ごとに 1 回実行されます。 .prerun<&Hook>().postrun<&Hook>() で登録します。必要なシグネチャは次の通りです: 生の 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) を呼び出します。

可変長引数 VDF

可変長引数 VDF は、任意の SQL 型の任意の数の引数を受け入れます。func ビルダーで .varargs() を宣言し、これは .no_params() および .param(TYPE) と排他です。本体は通常の固定引数ラッパーの代わりに vsql::VarArgs 引数を受け取ります。
可変長引数の登録には VEF Protocol 3 が必要です。古いサーバーはインストール時に拡張機能を拒否します。
フレームワークは可変長引数 VDF の引数カウントや型を検証できません。すべての可変長引数登録には、不正な入力時に PrerunResult::error() を呼び出すか、結果バッファのサイズ設定に PrerunResult::request_buffer_size(n) を呼び出す prerun フックをペアにしてください。 範囲 for ループで引数を反復処理します。各 AnyArg 要素は値を読み取る前に型チェックが必要です: どのアクセサでも is_null() をチェックしてください — 4 つすべてで null 引数に対する動作は未定義です。

VEF_GENERATE_REGISTRATION

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

型演算ビルダー

拡張機能がカスタム列型を定義する場合のみ必要です。関数のみを作成している場合は、Running Regression Tests にスキップしてください。 カスタム型には、エンジンが内部で呼び出す 3 つの演算が必要です:エンコード(文字列からバイナリ)、デコード(バイナリから文字列)、比較。ハッシュはオプションです。これらの C++ シグネチャに対して実装してください(すべて <villagesql/vsql.h> から利用可能):

固定長型

これらの演算を vsql::make_type<kTypeName>() を使用して登録します。型名は非型テンプレートパラメータ(NTTP)として渡されます — static constexpr const char[] 配列です。ビルダーはこの NTTP から TYPE::method 形式(例:"MYTYPE::from_string")の VDF 名を自動生成するため、手動の文字列マッチングは不要です。ビルド済みの型オブジェクトを拡張機能ビルダーの .type() に渡します。型演算用の個別の .func() 呼び出しは必要ありません。
型名は static constexpr const char[] 変数でなければなりません — 文字列リテラルを非型テンプレートパラメータとして使用することはできません。"MYTYPE" を直接渡すと、以下のようなコンパイラエラーが発生します:
以下に示すように、名前付き配列として宣言してください。
from_string, to_string, compare のいずれかが欠けている場合、build() はコンパイル時に失敗します。各テンプレートメソッドは static_assert を介して関数ポインタのシグネチャをチェックします。

組み込みデフォルト

IGNORE モード(例:INSERT IGNORE または UPDATE IGNORE)で NOT NULL カスタム型列に NULL が受け取られると、サーバーはエラーを発生させるのではなくフォールバック値を生成するために組み込みデフォルトを呼び出します。組み込みデフォルトは文字列表現を提供し、サーバーは型の from_string 関数を使用してそれをバイナリに変換します。
.intrinsic_default_str().intrinsic_default_vdf() の両方を省略した場合、 サーバーはフォールバックとして from_string("") を呼び出します。これは型が初めて使用される(テーブル作成時)に発生し、INSTALL EXTENSION 時ではありません。エンコード関数が空文字列を拒否する場合、または間違ったバイト数にエンコードする場合、型初期化は SQL クライアントで確認できるエラーで失敗します:
固定長型の場合、デフォルト文字列は正確に persisted_length バイトにエンコードされる必要があります。空文字列が有効な入力ではない型には明示的なデフォルトを設定してください。
文字列リテラル: .intrinsic_default_str() 定数デフォルトの場合、型ビルダーに文字列を直接渡します(上記の固定長の例で .intrinsic_default_str("0") と示されている通り)。 VDF ベース: .intrinsic_default_vdf() + make_intrinsic_default デフォルト値が型パラメータに依存する場合、これらのシグネチャのいずれかに基づいて関数を実装してください(<villagesql/vsql.h> から利用可能):
破壊的変更: IntrinsicDefaultFunc および IntrinsicDefaultWithParamsFuncconst char* の代わりに std::string を返します。 既存の組み込みデフォルト実装を直接 std::string を返すように更新してください。
デフォルト値の std::string 表現を返します。エラー時には error_msg にメッセージを書き込み、任意の値を返します(SDK は error_msg[0] != '\0' をチェックしてエラーを検出します)。make_intrinsic_default<&fn>("vdf_name")(引数 1 つ:VDF 名)で登録し、型ビルダーで .intrinsic_default_vdf() を使用してその名前を参照します。以下のパラメータ化された型の例に完全な登録パターンを示します。

パラメータ化された型

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

拡張機能システム変数

拡張機能システム変数はプレビュー機能です。完全な API リファレンス、ファクトリ関数、SQL アクセス、および完全な例については、Preview Capabilities を参照してください。

拡張機能ステータス変数

拡張機能ステータス変数はプレビュー機能です。完全な API リファレンス、ファクトリ関数、SQL アクセス、および完全な例については、Preview Capabilities を参照してください。

キーリングアクセス

キーリングアクセスはプレビュー機能です。完全な API リファレンス、結果コード、および完全な例については、Preview Capabilities を参照してください。

列ストレージ

列ストレージはプレビュー機能です。完全な API リファレンスおよび完全な例については、Preview Capabilities を参照してください。

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

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

回帰テストの実行

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

フルスイートの実行

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

個別テストの実行

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

新しいテストの作成

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

テストの場所

拡張機能のテストは 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 を実行し、アンインストールします:
テスト出力にテストランナーの一時ディレクトリのパスが含まれる場合、.test ファイル内にこのディレクティブを追加して正規化してください — これがないと、記録された結果には他のマシンで壊れる絶対パスが含まれます:

テストの追加手順

  1. 拡張機能の test/t/ ディレクトリに .test ファイルを作成する
  2. 拡張機能の test/r/ ディレクトリに 空の .result ファイルを作成する
  3. --record をつけてテストを実行し、期待される出力を生成します:
  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 ファイルの間の差分を出力します。
追加のデバッグ情報をつけてテストを実行するには:

関連資料

  • Creating Extensions — エンドツーエンドのビルド手順、CMake セットアップ、およびインストール
  • Extension API Reference — VDF 契約、null 処理、およびバッファサイズ設定
  • Extension Architecture — ライフサイクル、Victionary キャッシュ、パフォーマンスパターン、およびセキュリティモデル