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

# Rustにおけるプレビュー機能

> Rust SDK から VillageSQL のプレビュー機能を使用します — extension! の requires: リストで、ステータス変数、システム変数、バックグラウンドスレッドワーカー、キーリングアクセスを宣言します。

<Warning>
  Rust SDK はアルファ版です — リリース間で破壊的な API 変更が発生する可能性があります。
  プレビュー機能はさらにサーバー側でも不安定です。その ABI はサーバーのリリース間で
  変更される可能性があります。プレビュー機能に対してビルドされた拡張機能は、サーバーの
  アップデート後にロードに失敗する可能性があります。
</Warning>

プレビュー機能は、API が最終化される前に拡張機能に公開されるサーバー機能です。Rust SDK はそのうちの 4 つをラップしています：ステータス変数、システム変数、バックグラウンドスレッドワーカー、キーリングアクセスです。このページでは、それぞれを Rust から宣言して使用する方法を説明します。概念、プレビュー層、および C++ にのみ存在する機能については、[プレビュー機能](/docs/ja/mysql-8.4/stable/preview-capabilities)を参照してください。

<h2 id="prerequisites">
  前提条件
</h2>

プレビュー機能を使用する拡張機能は、`vsql_allow_preview_extensions` が `ON` の場合にのみインストールできます：

```sql theme={null}
SET PERSIST vsql_allow_preview_extensions = ON;
```

この変数に対して `SET GLOBAL` が拒否される理由を含む詳細については、[プレビュー層の有効化](/docs/ja/mysql-8.4/stable/preview-capabilities#enabling-the-preview-tier)を参照してください。

また、動作する Rust 拡張機能のセットアップも必要です — [Rust で拡張機能を作成する](/docs/ja/mysql-8.4/stable/rust-sdk)のチュートリアルで、ツールチェーン、`cargo-vsql`、および最初の `extension!` ブロックについて説明しています。

<h2 id="what-the-rust-sdk-wraps">
  Rust SDK がラップするもの
</h2>

| 機能                             | Rust モジュール                           | SDK の例                                                                                                    |
| ------------------------------ | ------------------------------------ | --------------------------------------------------------------------------------------------------------- |
| `vsql::status_var`             | `villagesql::preview::status_var`    | [`vsql_status_var`](https://github.com/villagesql/vsql-rust-sdk/tree/main/examples/vsql_status_var)       |
| `vsql::sys_var`                | `villagesql::preview::sys_var`       | [`vsql_sys_var`](https://github.com/villagesql/vsql-rust-sdk/tree/main/examples/vsql_sys_var)             |
| `vsql::preview::thread_worker` | `villagesql::preview::thread_worker` | [`vsql_thread_worker`](https://github.com/villagesql/vsql-rust-sdk/tree/main/examples/vsql_thread_worker) |
| `vsql::preview::keyring`       | `villagesql::preview::keyring`       | [`vsql_keyring`](https://github.com/villagesql/vsql-rust-sdk/tree/main/examples/vsql_keyring)             |

残りのプレビュー機能 — `auth`、`mysql_services`、`sql_query`、`statement_event`、および列ストレージ ABI — は現時点では C++ のみです。これらのいずれかが必要な場合は、[C++ SDK](/docs/ja/mysql-8.4/stable/preview-capabilities) を使用してください。

<h2 id="registration-pattern">
  登録パターン
</h2>

各機能を `static` として宣言し、`extension!` の `requires:` セクションで参照によって列挙します — これは C++ SDK の `.with()` に相当する Rust の方法です：

```rust theme={null}
use villagesql::preview::keyring::KeyringCapability;

static KEYRING: KeyringCapability = KeyringCapability::new();

villagesql::extension! {
    funcs: [
        // ...
    ],
    requires: [
        &KEYRING,
    ]
}
```

サーバーはロード時に機能オブジェクトに値を設定します。それ以前は、アクセサメソッドはクラッシュするのではなく、機能が利用不可であることを報告します。`static` は必須です — サーバーは拡張機能の全生存期間にわたって機能へのポインタを保持します。

<h2 id="status-variables">
  ステータス変数
</h2>

`status_var` 機能は、拡張機能が所有するカウンターを `SHOW GLOBAL STATUS` を通じて公開します。拡張機能は `'static` なアトミック値としてストレージを所有し、それらに書き込みます。サーバーは、ステータス変数がクエリされるたびにポインタ経由で読み取ります。

各変数を `StatusVarSpec` として宣言します — `AtomicI64` に裏付けられた `Int`、または SDK の `AtomicF64` に裏付けられた `Double` です（Rust の標準ライブラリにはアトミックな `f64` がないため、SDK が `new`、`load`、`store` を備えたものを提供しています）：

```rust theme={null}
use std::sync::atomic::{AtomicI64, Ordering};

use villagesql::preview::status_var::{AtomicF64, StatusVarCapability, StatusVarSpec};
use villagesql::{InValue, VdfReturn};

static REQUESTS: AtomicI64 = AtomicI64::new(0);
static LOAD: AtomicF64 = AtomicF64::new(0.5);

static SPECS: &[StatusVarSpec] = &[
    StatusVarSpec::Int {
        name: c"requests",
        value: &REQUESTS,
    },
    StatusVarSpec::Double {
        name: c"load",
        value: &LOAD,
    },
];

static STATUS_VAR: StatusVarCapability = StatusVarCapability::new(SPECS);

fn bump_impl(_args: &[InValue]) -> VdfReturn {
    let n = REQUESTS.fetch_add(1, Ordering::Relaxed) + 1;
    VdfReturn::int(n)
}

villagesql::extension! {
    funcs: [
        villagesql::func!(bump_impl, "bump", [] -> villagesql::Type::Int),
    ],
    requires: [
        &STATUS_VAR,
    ]
}
```

`INSTALL EXTENSION` の後、変数は拡張機能名をプレフィックスとして表示されます：

```sql theme={null}
SELECT vsql_status_var.bump();
-- 1
SHOW GLOBAL STATUS LIKE 'vsql_status_var%';
```

```
Variable_name	Value
vsql_status_var.load	0.500000
vsql_status_var.requests	1
```

<h2 id="system-variables">
  システム変数
</h2>

`sys_var` 機能は、拡張機能が所有する MySQL システム変数を登録します。3 つの型がサポートされます：`Bool`、`Int`（`min`/`max` の境界付き）、および `Str` です。すべての spec は、名前、`SHOW VARIABLES` のメタデータに表示されるコメント、デフォルト値、およびオプションの `on_change` コールバックを持ちます。

名前と文字列のデフォルト値は `&'static CStr` の値です — C 文字列リテラル（`c"enabled"`）として記述してください：

```rust theme={null}
use villagesql::preview::sys_var::{SysVarCapability, SysVarSpec};

static SPECS: &[SysVarSpec] = &[
    SysVarSpec::Bool {
        name: c"enabled",
        comment: c"Enable the feature",
        default: true,
        on_change: None,
    },
    SysVarSpec::Int {
        name: c"threshold",
        comment: c"Threshold in milliseconds",
        default: 1000,
        min: 0,
        max: 60000,
        on_change: None,
    },
    SysVarSpec::Str {
        name: c"log_path",
        comment: c"Path to the log file",
        default: c"/tmp/vsql_sys_var.log",
        on_change: None,
    },
];

static SYS_VAR: SysVarCapability = SysVarCapability::new(SPECS);

villagesql::extension! {
    funcs: [],
    requires: [
        &SYS_VAR,
    ]
}
```

`funcs:` セクションは、空の場合でも `requires:` の前に存在しなければなりません。

インストール後、変数は拡張機能名をプレフィックスとして参照できます：

```sql theme={null}
SELECT @@global.vsql_sys_var.enabled;
-- 1
SELECT @@global.vsql_sys_var.threshold;
-- 1000
SET GLOBAL vsql_sys_var.enabled = 0;
```

<h3 id="reacting-to-changes">
  変更への反応
</h3>

`on_change` は生の C コールバックであり、変数が設定された後にサーバーによって呼び出されます。サーバーはグローバルなシステム変数ロックを保持したままこれを呼び出すため、処理は短く保ち、パニックさせないでください — ここでのパニックは FFI 境界を越えます。コールバックは生の ABI レイヤーから `*const vef_sys_var_change_t` を受け取ります：

```rust theme={null}
use std::sync::atomic::{AtomicU64, Ordering};
use villagesql::sys::vef_sys_var_change_t;

static CHANGE_COUNT: AtomicU64 = AtomicU64::new(0);

unsafe extern "C" fn on_enabled_change(_change: *const vef_sys_var_change_t) {
    CHANGE_COUNT.fetch_add(1, Ordering::Relaxed);
}
```

<Warning>
  `SysVarCapability::get` または `SysVarCapability::set` の呼び出し、SQL の実行、あるいはそのいずれかを行うスレッドの待機は、そのロックでデッドロックします。コールバックは上記のように自身の static への記録のみに留め、SQL を必要とする処理は[スレッドワーカー](#thread-worker)に渡してください。
</Warning>

`on_change: Some(on_enabled_change)` として spec に組み込みます。

<h3 id="reading-and-writing-from-extension-code">
  拡張機能コードからの読み書き
</h3>

`SysVarCapability` は、サーバーを介したプログラム的アクセスのための `get()` および `set()` も公開しています（そのため範囲の検証と永続化はサーバーが処理します）。`set()` は永続化を選択する `scope` 引数を取ります：`null` は実行中の値のみを変更するため、再起動時に元に戻ります。`"PERSIST"` は実行中の値を変更し、永続化された設定にも書き込みます。`"PERSIST_ONLY"` は実行中の値に触れずに永続化された設定に書き込むため、次回の再起動時に適用されます。どちらも NUL 終端の C 文字列を取る `unsafe` な FFI メソッドであり、どちらも反転した C の慣習を使用します：`Some(false)` は成功、`Some(true)` はサーバーがエラーを報告したこと、`None` は機能が利用不可であることを意味します。`get` が成功した場合、サーバーは `malloc` された文字列を書き込みます。これは C の `free()` で解放する必要があります。[`vsql_sys_var` の例](https://github.com/villagesql/vsql-rust-sdk/blob/main/examples/vsql_sys_var/src/lib.rs)に、`free` の extern 宣言と safety コメントを含む完全なパターンが示されています。

<h2 id="thread-worker">
  スレッドワーカー
</h2>

`thread_worker` 機能は、あなたが提供する関数をサーバー管理のバックグラウンドスレッドで実行します。サーバーはロード時に制御システム変数を登録します。それが `ON` の間、あなたの作業関数は定期タイマー、ファイルディスクリプタの準備完了、または有効化/無効化の遷移に応じて呼び出されます。

作業関数は通常の安全な Rust です：

```rust theme={null}
use std::sync::atomic::{AtomicI64, Ordering};
use std::time::Duration;

use villagesql::preview::thread_worker::{
    NextWakeup, ThreadHandle, ThreadWorkerCapability, WakeupReason,
};
use villagesql::{InValue, VdfReturn};

static TICKS: AtomicI64 = AtomicI64::new(0);

fn worker(reason: WakeupReason, _handle: ThreadHandle) -> NextWakeup {
    if reason == WakeupReason::Periodic {
        TICKS.fetch_add(1, Ordering::Relaxed);
    }
    NextWakeup::unchanged()
}

static WORKER: ThreadWorkerCapability =
    ThreadWorkerCapability::new(worker, "ticker", Duration::from_millis(100), None);

fn ticks_impl(_args: &[InValue]) -> VdfReturn {
    VdfReturn::int(TICKS.load(Ordering::Relaxed))
}

villagesql::extension! {
    funcs: [
        villagesql::func!(ticks_impl, "ticks", [] -> villagesql::Type::Int),
    ],
    requires: [
        &WORKER,
    ]
}
```

`ThreadWorkerCapability::new` は、作業関数、スレッド名のサフィックス、初期スリープ間隔、およびオプションの制御変数名のオーバーライドを取ります。オーバーライドが `None` の場合、制御変数は `{suffix}_enabled` という名前になり、拡張機能のプレフィックスの下に登録されます：

```sql theme={null}
SHOW GLOBAL VARIABLES LIKE '%ticker%';
```

```
Variable_name	Value
vsql_thread_worker.ticker_enabled	OFF
```

```sql theme={null}
SET GLOBAL vsql_thread_worker.ticker_enabled = ON;
SELECT SLEEP(0.5);
SELECT vsql_thread_worker.ticks();
-- 4  (varies with timing — one tick per 100 ms while enabled)
SET GLOBAL vsql_thread_worker.ticker_enabled = OFF;
```

<h3 id="wakeups">
  ウェイクアップ
</h3>

`WakeupReason` は、サーバーが呼び出した理由を示します：`Enable`、`Periodic`、`PollFd`、または `Disable` です。戻り値によって次回のウェイクアップを調整します：

* `NextWakeup::unchanged()` — 現在のスリープ間隔と poll fd を維持します。
* `NextWakeup::after(duration)` — `duration` の後に再度起動します。
* `poll_fd` フィールドに 0 より大きいファイルディスクリプタを設定すると、それが読み取り可能になったときにも起動します。`-1` を設定すると、以前に設定したものをクリアします。

長さ 0 の `Duration` は「変更なし」に帰着します — 基盤となる C ABI が `0` をその意味に予約しているため、即時のウェイクアップは表現できません。

作業関数がパニックした場合、SDK は FFI 境界でそのパニックを捕捉し、その呼び出しが `NextWakeup::unchanged()` を返したものとして扱います — ワーカーは実行を継続します。

`ThreadHandle` パラメータは、`sql_query` 機能が Rust に移植された後にワーカーから SQL セッションを開くために予約されています。現時点ではメソッドを持ちません。

<h2 id="keyring-access">
  キーリングアクセス
</h2>

`keyring` 機能は、MySQL keyring コンポーネントに格納されたシークレット — API キー、暗号化キー、テーブルに置くべきでないあらゆるもの — を読み書きします。サーバーに keyring コンポーネント（例：`component_keyring_file`）がインストールされている必要があります。インストールされていない場合、すべての読み書きは `KeyringError::NoComponent` で失敗します。

```rust theme={null}
use std::ffi::CString;

use villagesql::preview::keyring::KeyringCapability;
use villagesql::{InValue, VdfReturn};

static KEYRING: KeyringCapability = KeyringCapability::new();

const MAX_SECRET_LEN: usize = 1024;

fn keyring_read_impl(args: &[InValue]) -> VdfReturn {
    let Some(&InValue::String(data_id)) = args.first() else {
        return VdfReturn::null();
    };
    let Ok(data_id) = CString::new(data_id) else {
        return VdfReturn::null();
    };

    let mut buf = [0u8; MAX_SECRET_LEN];
    match KEYRING.read(&data_id, None, &mut buf) {
        Ok(Some(n)) => match std::str::from_utf8(&buf[..n]) {
            Ok(s) => VdfReturn::string(s),
            Err(_) => VdfReturn::null(),
        },
        Ok(None) | Err(_) => VdfReturn::null(),
    }
}

villagesql::extension! {
    funcs: [
        villagesql::func!(
            keyring_read_impl, "keyring_read",
            [villagesql::Type::String] -> villagesql::Type::String,
            buffer_size: MAX_SECRET_LEN
        ),
    ],
    requires: [
        &KEYRING,
    ]
}
```

`read(data_id, auth_id, buf)` は、渡されたバッファを埋め、シークレットの長さとともに `Ok(Some(n))` を返します。`data_id` の下にシークレットが存在しない場合は `Ok(None)` を返します — これはエラーではなく通常の結果です。`write(data_id, auth_id, data)` は成功時に `Ok(())` を返します。`auth_id` は所有ユーザーです。特定のユーザーに関連付けられていない内部キーには `None` を渡します。

どちらも失敗時に `Err(KeyringError)` を返します：`CapabilityUnavailable`（機能が一度も接続されていない）、`NoComponent`（サーバーに keyring コンポーネントがない）、または `Other` です。

keyring にはサイズを調べる手段がありません。`read` に渡したバッファより大きいシークレットは `Ok(None)` として返り、キーが存在しない場合と区別できません。格納する見込みの最大のシークレットに合わせてバッファのサイズを決めてください。

<h2 id="next-steps">
  次のステップ
</h2>

<CardGroup cols={2}>
  <Card title="プレビュー機能（C++）" icon="flask" href="/docs/ja/mysql-8.4/stable/preview-capabilities">
    完全な機能インデックス、プレビュー層、および C++ のみの機能：auth、
    mysql\_services、sql\_query、statement\_event、列ストレージ。
  </Card>

  <Card title="Rust API リファレンス" icon="book" href="/docs/ja/mysql-8.4/stable/rust-api-reference">
    InValue、VdfReturn、extension!、func!、custom\_type! — すべてのフィールド。
  </Card>
</CardGroup>
