> ## 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 中的自定义类型

> 在 VillageSQL Rust SDK 中定义新的列类型——二进制布局、编码、解码、比较、哈希和算术函数，使用 `custom_type!` 宏。

<Warning>
  Rust SDK 处于 alpha 阶段——各版本之间可能出现破坏性的 API 变更。已支持
  仅含函数的扩展和自定义类型（编码、解码、比较、哈希）。聚合、`prerun()`、
  `VarArgs`、系统变量和状态变量、密钥环访问以及列存储 ABI 目前仅在 C++
  中可用——如果您需要其中任何功能，请使用 [C++ SDK](/docs/zh/mysql-8.4/0.0.5/create)。
</Warning>

自定义类型允许您定义新的列类型——例如 `RATIONAL`、`VECTOR` 或 `INET`——这些类型可以与 `ORDER BY`、索引和聚合函数一起使用。Rust SDK 通过 `custom_type!` 宏支持此功能。

本页假设您已经完成了 [使用 Rust 构建扩展](/docs/zh/mysql-8.4/0.0.5/rust-sdk) 的相关步骤。设置（Cargo.toml、manifest.json、cargo-vsql）与之前相同。

## 何时使用自定义类型

在以下情况下使用自定义类型：

* 您需要一种二进制的磁盘布局，而标准的 SQL 类型无法表达这种布局（打包的浮点数、固定宽度的整数、二进制标识符）
* 您的类型具有其自身的排序语义，该语义与词法字符串排序不同
* 您希望服务器正确地索引和哈希值，以便进行 `ORDER BY`、`COUNT(DISTINCT)` 和集合操作

如果您只需要可从 SQL 调用的函数，并且您的数据可以轻松地放入 `STRING`、`INT` 或 `REAL` 列中，则不需要自定义类型。

## `custom_type!` 宏

每个自定义类型都需要 4 个回调函数（编码、解码、比较、哈希）和一个默认值。以下是完整的宏签名：

```rust theme={null}
villagesql::custom_type!(
    type_name: "type_name_in_sql",
    persisted_length: N,
    max_decode_buffer_length: M,
    encode: your_encode_fn,
    decode: your_decode_fn,
    compare: your_compare_fn,
    hash: your_hash_fn,
    default: "a_valid_string_literal",
)
```

| 字段                         | 类型      | 描述                                                                   |
| -------------------------- | ------- | -------------------------------------------------------------------- |
| `type_name`                | 字符串字面量  | SQL 类型名称。在 SQL 中不区分大小写。                                              |
| `persisted_length`         | `usize` | 用于磁盘存储的固定字节长度。                                                       |
| `max_decode_buffer_length` | `usize` | 解码后的字符串表示形式的最大字节长度。                                                  |
| `encode`                   | 函数      | 将 `&str` 转换为 INSERT 时的二进制字节。                                         |
| `decode`                   | 函数      | 将二进制字节转换回 `String` 以进行显示。                                            |
| `compare`                  | 函数      | 返回 `Ordering`，用于 `ORDER BY`、`MIN`、`MAX`。                             |
| `hash`                     | 函数      | 返回 `usize` 哈希值，用于 `COUNT(DISTINCT)` 和集合操作。可选，但建议为索引列使用。              |
| `default`                  | 字符串字面量  | 一个有效的字符串，服务器可以在类型初始化时对其进行编码。必须编码为恰好 `persisted_length` 个字节。可选，但建议使用。 |

`type_name`、`persisted_length`、`max_decode_buffer_length`、`encode`、`decode` 和 `compare` 是必需的。`hash` 和 `default` 是可选的，但建议使用——`hash` 对于正确的 `COUNT(DISTINCT)` 和集合操作是必需的，`default` 用于类型初始化验证。

## 接收和返回二进制值

接受或返回自定义类型的函数使用原始字节。

**输入**——`InValue::Custom(b)` 携带存储的二进制数据，类型为 `&[u8]`：

```rust theme={null}
fn rational_numer_impl(args: &[InValue]) -> VdfReturn {
    match args.first() {
        Some(InValue::Custom(b)) => {
            let numer = read_i64(b, 0);
            VdfReturn::int(numer)
        }
        Some(InValue::Null) | None => VdfReturn::null(),
        _ => VdfReturn::error("rational_numer: expected a RATIONAL argument"),
    }
}
```

**输出**——`VdfReturn::Binary(bytes)` 将二进制字节发送回服务器：

```rust theme={null}
fn rational_add_impl(args: &[InValue]) -> VdfReturn {
    match (args.get(0), args.get(1)) {
        (Some(InValue::Custom(a)), Some(InValue::Custom(b))) => {
            let result = add_rationals(a, b);
            VdfReturn::Binary(result)
        }
        _ => VdfReturn::null(),
    }
}
```

要在 `func!` 声明中引用自定义类型，请使用 `villagesql::custom!("type_name")`：

```rust theme={null}
villagesql::func!(
    rational_add_impl,
    "rational_add",
    [villagesql::custom!("rational"), villagesql::custom!("rational")] -> villagesql::custom!("rational"),
    deterministic: true
)
```

## 示例：有理数类型

SDK 仓库中的 `examples/vsql_rational` 是一个可运行的扩展示例，它实现了 `RATIONAL` 类型。它以小端字节顺序存储一个有理数，作为 16 字节的 `i64` 值对（分子、分母），并提供算术函数。

以下是核心的编码、解码、比较和哈希实现：

```rust theme={null}
use villagesql::{InValue, VdfReturn};

// Binary layout: [numerator: i64 LE][denominator: i64 LE] — 16 bytes total.
// Always stored in reduced form (GCD = 1) with a positive denominator.
const BYTES: usize = 16;

fn to_bytes(num: i64, den: i64) -> Vec<u8> {
    let mut v = Vec::with_capacity(BYTES);
    v.extend_from_slice(&num.to_le_bytes());
    v.extend_from_slice(&den.to_le_bytes());
    v
}

fn from_bytes(b: &[u8]) -> (i64, i64) {
    let num = i64::from_le_bytes(b[..8].try_into().unwrap());
    let den = i64::from_le_bytes(b[8..16].try_into().unwrap());
    (num, den)
}

// encode: "3/4" -> 16 bytes
pub fn rational_encode(s: &str) -> Result<Vec<u8>, String> {
    let (num_s, den_s) = s
        .split_once('/')
        .ok_or_else(|| format!("rational: expected 'n/d', got {:?}", s))?;
    let num: i64 = num_s.trim().parse()
        .map_err(|e| format!("rational numerator: {}", e))?;
    let den: i64 = den_s.trim().parse()
        .map_err(|e| format!("rational denominator: {}", e))?;
    let (n, d) = normalize(num as i128, den as i128)
        .ok_or_else(|| "rational: zero or overflowing denominator".to_string())?;
    Ok(to_bytes(n, d))
}

// decode: 16 bytes -> "3/4"
pub fn rational_decode(b: &[u8]) -> Result<String, String> {
    if b.len() < BYTES {
        return Err(format!("rational: expected {} bytes, got {}", BYTES, b.len()));
    }
    let (n, d) = from_bytes(b);
    Ok(format!("{}/{}", n, d))
}

// compare: for ORDER BY, MIN, MAX
pub fn rational_compare(a: &[u8], b: &[u8]) -> std::cmp::Ordering {
    let (n1, d1) = from_bytes(a);
    let (n2, d2) = from_bytes(b);
    // cross-multiply (denominators are always positive)
    let lhs = (n1 as i128) * (d2 as i128);
    let rhs = (n2 as i128) * (d1 as i128);
    lhs.cmp(&rhs)
}

// hash: for COUNT(DISTINCT) and set operations
pub fn rational_hash(b: &[u8]) -> usize {
    // FNV-1a over the 16 bytes
    let mut h: usize = 0xcbf29ce484222325u64 as usize;
    for &byte in b {
        h ^= byte as usize;
        h = h.wrapping_mul(0x100000001b3u64 as usize);
    }
    h
}
```

`custom_type!` 注册和算术 VDF（`rational_add`、`rational_sub` 等）都在完整的源代码 `examples/vsql_rational/src/lib.rs` 中。

安装扩展后：

```sql theme={null}
INSTALL EXTENSION vsql_rational;

CREATE TABLE fractions (
    id   INT PRIMARY KEY,
    val  RATIONAL
);

INSERT INTO fractions VALUES (1, '1/2'), (2, '3/4'), (3, '1/4');

-- ORDER BY uses rational_compare
SELECT val FROM fractions ORDER BY val;
-- → 1/4, 1/2, 3/4

-- Arithmetic with rational_add
SELECT rational_add('1/3', '1/6');
-- → 1/2

-- Extract numerator and denominator
SELECT rational_numer(val), rational_denom(val) FROM fractions;

-- Convert to floating-point approximation
SELECT rational_to_real('1/3');
-- → 0.3333333333333333
```

`rational_to_real(r RATIONAL) -> REAL` 将 `RATIONAL` 值转换为 64 位浮点近似值，方法是将分子除以分母。当您需要用于显示或比较的近似十进制数，但又不想在列中存储有损表示形式时，此函数很有用。

## 包含类型和函数的 `extension!` 块

在注册函数和类型时，`extension!` 块有两个部分：

```rust theme={null}
villagesql::extension! {
    funcs: [
        // VDFs declared with func!
    ],
    types: [
        // Custom types declared with custom_type!
    ]
}
```

如果为空，则可以省略任一部分。仅包含类型的扩展会省略 `funcs:`；仅包含函数的扩展会省略 `types:`。

## 后续步骤

<CardGroup cols={2}>
  <Card title="Rust API 参考" icon="book" href="/docs/zh/mysql-8.4/0.0.5/rust-api-reference">
    关于 `InValue`、`VdfReturn` 和所有宏的完整参考。
  </Card>

  <Card title="用 Rust 构建扩展" icon="wrench" href="/docs/zh/mysql-8.4/0.0.5/rust-sdk">
    入门——Cargo 设置、第一个函数、打包和测试。
  </Card>

  <Card title="C++ 自定义类型" icon="shapes" href="/docs/zh/mysql-8.4/0.0.5/custom-types">
    C++ 中的自定义类型——`make_type<>`、编码/解码/比较/哈希、`ALTER TABLE` 规则。
  </Card>

  <Card title="扩展架构" icon="sitemap" href="/docs/zh/mysql-8.4/0.0.5/architecture">
    自定义类型是如何解析、缓存和存储的。
  </Card>
</CardGroup>
