> ## 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: リストで、ステータス変数、システム変数、バックグラウンドスレッドワーカー、SQL クエリ、キーリングアクセスを宣言します。

> [!WARNING]
>
> プレビュー機能APIは安定していません。プレビュー機能に依存して構築された拡張機能は、サーバーのアップデート後にロードに失敗する可能性があります。

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

## 前提条件

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

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

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

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

## Rust SDK がラップするもの

| 機能 | 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::sql_query` | `villagesql::preview::sql_query` | [`vsql_sql_query`](https://github.com/villagesql/vsql-rust-sdk/tree/main/examples/vsql_sql_query) |
| `vsql::preview::keyring` | `villagesql::preview::keyring` | [`vsql_keyring`](https://github.com/villagesql/vsql-rust-sdk/tree/main/examples/vsql_keyring) |

残りのプレビュー機能 — `auth`、`mysql_services`、`session_var`、`statement_event`、カスタムインデックス機能、および列ストレージ ABI — は現時点では C++ のみです。これらのいずれかが必要な場合は、[C++ SDK](https://villagesql.com/docs/ja/extensions/build/preview-capabilities.md) を使用してください。

## 登録パターン

各機能を `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` は必須です — サーバーは拡張機能の全生存期間にわたって機能へのポインタを保持します。

## ステータス変数

`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
```

## システム変数

`sys_var` 機能は、拡張機能が所有する MySQL システム変数を登録します。3 つの型がサポートされます：`Bool`、`Int`（`min`/`max` の境界付き）、および `Str` です。すべての spec は、名前、コメント、デフォルト値、およびオプションの `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;
```

### 変更への反応

`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)に渡してください。

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

### 拡張機能コードからの読み書き

`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 コメントを含む完全なパターンが示されています。

## スレッドワーカー

`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;
```

### ウェイクアップ

`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()` を返したものとして扱います — ワーカーは実行を継続します。

`handle` パラメータを [`SqlQueryCapability::open`](#sql-query) に渡すと、ワーカー内から SQL を実行できます。

## SQL クエリ

`sql_query` 機能は、`thread_worker` のコールバックから SQL セッションを開きます。これにより、バックグラウンドワーカーは、自身がロードされているサーバーに対してステートメントを実行できます。`ThreadWorkerCapability` と一緒に宣言し、ワーカーが受け取る `ThreadHandle` でセッションを開きます：

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

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

static USER_COUNT: AtomicI64 = AtomicI64::new(-1);

static SQL_QUERY: SqlQueryCapability = SqlQueryCapability::new();

fn worker(reason: WakeupReason, handle: &ThreadHandle) -> NextWakeup {
    if reason == WakeupReason::Periodic {
        if let Some(session) = SQL_QUERY.open(handle) {
            if let Some(mut result) = session.execute("SELECT COUNT(*) FROM mysql.user") {
                if !result.has_error() {
                    if let Some(n) = result.next_row().and_then(|row| row.get_int(0)) {
                        USER_COUNT.store(n, Ordering::Relaxed);
                    }
                }
            }
        }
    }
    NextWakeup::unchanged()
}

static WORKER: ThreadWorkerCapability =
    ThreadWorkerCapability::new(worker, "user_count", Duration::from_secs(5), None);

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

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

`SqlQueryCapability::open(handle)` は、機能に値が設定されていない場合（プレビューがオフ、または要求されていない）、ABI が古すぎる場合、あるいはサーバーがセッションを開けなかった場合に `None` を返します。結果を使用する前に確認してください。

`Session` には、ステートメントを実行する 2 つの方法があります：

* `execute(sql)` は結果セット全体をバッファリングし、`QueryResult` を返します。`next_row()` が `None` を返すまで繰り返し呼び出します。各 `Row` は `get_int(i)`、`get_str(i)`、`get_real(i)`、または `get_bytes(i)` で列を読み取ります。`get_bytes`/`get_str` は、SQL `NULL` または範囲外のインデックスに対して `None` を返します（`get_str` は UTF-8 でないバイトに対しても `None` を返します）。`get_int`/`get_real` はまず列をテキストとして解析するため、解析できないテキストに対しても `None` を返します。`execute` 自体は、セットアップの失敗（セッションがない、またはメモリ不足）の場合にのみ `None` を返します。
* `for_each(sql, |row: Row<'_>| { ... })` は、結果セット全体をバッファリングする代わりに、行をクロージャにストリーミングします。返される `QueryResult` は診断情報のみを持ち、反復処理するバッファリングされた行はありません。また、セットアップの失敗時には `None` を返します。

ステートメントが成功したと判断する前に、どちらの結果でも `has_error()` と `error()`（`errno`、`severity`、`sqlstate`、`message` を持つ `Option<Diag>`）を確認してください。`warnings()` は、結果に付随するすべての警告とノートを返します。

`Session` と `QueryResult` は、それぞれドロップ時に基盤となるハンドルを閉じます。自分で呼び出す close メソッドはありません。

## キーリングアクセス

`keyring` 機能は、MySQL keyring コンポーネントに格納されたシークレット — API キー、暗号化キー、テーブルに置くべきでないあらゆるもの — を読み書きします。keyring コンポーネント（例：`component_keyring_file`）は、[Keyring Component Installation](https://dev.mysql.com/doc/refman/8.4/en/keyring-component-installation.html) で説明されているように、コンポーネントマニフェストとその構成ファイルを通じてサーバー起動時にロードされている必要があります。`INSTALL COMPONENT` では利用可能になりません。コンポーネントがない場合、すべての読み書きは `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) => VdfReturn::null(),
        Err(e) => VdfReturn::error(format!("keyring: {e}")),
    }
}

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)` として返り、キーが存在しない場合と区別できません。格納する見込みの最大のシークレットに合わせてバッファのサイズを決めてください。

## 次のステップ

> [card] [プレビュー機能（C++）](https://villagesql.com/docs/ja/extensions/build/preview-capabilities.md)
>
> 完全な機能インデックス、プレビュー層、および C++ のみの機能：auth、
> mysql\_services、session\_var、statement\_event、カスタムインデックス機能、
> 列ストレージ。

> [card] [Rust API リファレンス](https://villagesql.com/docs/ja/sdk/rust/api-reference.md)
>
> InValue、VdfReturn、extension!、func!、custom\_type! — すべてのフィールド。


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