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

# Tipos Personalizados em Rust

> Defina novos tipos de coluna no SDK Rust do VillageSQL — layout binário, encode, decode, compare, hash e funções aritméticas com a macro custom_type!.

Tipos personalizados permitem que você defina novos tipos de coluna, como `RATIONAL`, `VECTOR` ou `INET`, que funcionam com `ORDER BY`, índices e funções de agregação. O SDK Rust oferece suporte a isso por meio da macro `custom_type!` e, para tipos cujo tamanho de armazenamento depende de parâmetros de coluna, por meio de `parameterized_type!` (consulte [Tipos parametrizados](#parameterized-types)).

Esta página pressupõe que você já tenha trabalhado com [Criando Extensões em Rust](https://villagesql.com/docs/pt-BR/extensions/build/rust-sdk.md). A configuração (Cargo.toml, manifest.json, cargo-vsql) é a mesma.

## Quando usar um tipo personalizado

Use um tipo personalizado quando:

* Você precisar de um layout binário em disco que um tipo SQL padrão não consegue expressar (floats compactados, inteiros de largura fixa, identificadores binários)
* Seu tipo tiver semântica de ordenação própria que difere da ordenação lexicográfica de strings
* Você quiser que o servidor indexe e faça hash dos valores corretamente para `ORDER BY`, `GROUP BY`, `DISTINCT` e operações de conjunto

Se você precisar apenas de funções chamáveis via SQL e seus dados couberem confortavelmente em colunas `STRING`, `INT` ou `REAL`, você não precisa de um tipo personalizado.

## A macro custom\_type!

Todo tipo personalizado precisa de 3 callbacks (encode, decode, compare), um valor padrão e, opcionalmente, um callback de hash. Aqui está a assinatura completa da macro:

```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",
)
```

| Campo | Tipo | Descrição |
| - | - | - |
| `type_name` | literal de string | O nome do tipo SQL. Não diferencia maiúsculas de minúsculas em SQL. |
| `persisted_length` | `i64` | Comprimento fixo em bytes para o armazenamento em disco. |
| `max_decode_buffer_length` | `i64` | Comprimento máximo em bytes da representação de string decodificada. |
| `encode` | fn | Converte um `&str` em bytes binários no momento do INSERT. |
| `decode` | fn | Converte bytes binários de volta em uma `String` para exibição. |
| `compare` | fn | Retorna `Ordering` para `ORDER BY`, `MIN`, `MAX`. |
| `hash` | fn | Retorna um hash `usize` para `GROUP BY`, `DISTINCT` e operações de conjunto como `UNION`. Opcional, mas recomendado. |
| `default` | literal de string | Uma string válida que o servidor consegue codificar na inicialização do tipo. Deve codificar para exatamente `persisted_length` bytes. Obrigatório, a menos que `intrinsic_default_fn` seja fornecido. |
| `intrinsic_default_fn` | fn | Calcula o padrão em vez de declará-lo: `fn() -> Result<String, String>`. Use-o quando o padrão não puder ser escrito como literal. |

`type_name`, `persisted_length`, `max_decode_buffer_length`, `encode`, `decode` e `compare` são obrigatórios. `hash` é opcional, mas recomendado, porque `GROUP BY`, `DISTINCT` e as operações de conjunto corretos dependem dele. Dê ao tipo um padrão de uma forma ou de outra: `default` para um valor que você consegue escrever como literal, `intrinsic_default_fn` para um que a extensão precisa calcular. Definir ambos faz o `INSTALL EXTENSION` falhar. Não definir nenhum deixa a extensão ser instalada e, então, faz falhar a primeira instrução que nomeia o tipo.

## Recebendo e retornando valores binários

Funções que recebem ou retornam um tipo personalizado trabalham com bytes brutos.

**Entrada** — `InValue::Custom(b)` carrega o binário armazenado como `&[u8]`:

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

**Saída** — `VdfReturn::Binary(bytes)` envia bytes binários de volta ao servidor:

```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(),
    }
}
```

Para referenciar um tipo personalizado em uma declaração `func!`, use `villagesql::custom!("type_name")`:

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

## Exemplo: tipo de número racional

`examples/vsql_rational` no repositório do SDK é uma extensão funcional que implementa um tipo `RATIONAL`. Ela armazena um número racional como um par de valores `i64` (numerador, denominador, totalizando 16 bytes) em ordem de bytes little-endian e fornece funções aritméticas.

Aqui estão as implementações principais de encode, decode, compare e hash:

```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 GROUP BY, 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
}
```

O registro `custom_type!`, os auxiliares `gcd` e `normalize` que `rational_encode` chama, e as VDFs aritméticas (`rational_add`, `rational_sub`, etc.) estão no código-fonte completo em `examples/vsql_rational/src/lib.rs`.

Com a extensão instalada:

```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` converte um valor `RATIONAL` em uma aproximação de ponto flutuante de 64 bits, dividindo o numerador pelo denominador. Útil quando você precisa de um decimal aproximado para exibição ou comparação, mas não quer armazenar a representação com perda na coluna.

## Tipos parametrizados

Um tipo parametrizado precisa de um valor lido no momento do `CREATE TABLE` — como
o `3` em `VECTOR(3)` — para saber seu tamanho de armazenamento. `custom_type!` não
consegue expressar isso: seu `persisted_length` é uma única constante fixa para toda
coluna. `parameterized_type!` é a contraparte parametrizada: o comprimento persistido
é calculado por coluna a partir dos parâmetros declarados, através de `int_to_params`
e `resolve_params`, em vez de ser fixo.

```rust theme={null}
villagesql::parameterized_type!(
    type_name: "type_name_in_sql",
    max_persisted_length: N,
    max_decode_buffer_length: M,
    encode: your_encode_fn,
    decode: your_decode_fn,
    compare: your_compare_fn,
    int_to_params: your_int_to_params_fn,
    resolve_params: your_resolve_params_fn,
    params_type: YourParamsType,
    params_parse: your_params_parse_fn,
    params_to_strings: your_params_to_strings_fn,
    hash: your_hash_fn,
    default: "a_valid_string_literal",
)
```

| Campo | Tipo | Descrição |
| - | - | - |
| `type_name` | literal de string | O nome do tipo SQL. Não diferencia maiúsculas de minúsculas em SQL. |
| `max_persisted_length` | `i64` | Limite superior do comprimento em bytes em disco, considerando todas as parametrizações válidas. |
| `max_decode_buffer_length` | `i64` | Comprimento máximo em bytes da representação de string decodificada. |
| `encode` | fn | Converte um `&str` mais um `&mut MaybeParams<P>` em bytes binários no momento do INSERT. |
| `decode` | fn | Converte bytes binários mais um `&P` de volta em uma `String` para exibição. |
| `compare` | fn | Retorna `Ordering` para `ORDER BY`, `MIN`, `MAX`, dado um `&P`. |
| `int_to_params` | fn | Converte a sintaxe de inteiro `TYPE(N)` em uma string de parâmetros canônica. |
| `resolve_params` | fn | Valida um `Params` analisado e retorna um `Resolved` (tamanhos de armazenamento e, opcionalmente, parâmetros reescritos). |
| `params_type` | tipo | Sua struct de parâmetros analisados, por exemplo `PadintParams`. |
| `params_parse` | fn | Analisa `Params` em `params_type`. É executada no máximo uma vez por combinação única de parâmetros — o SDK armazena o resultado em cache. |
| `params_to_strings` | fn | O inverso de `params_parse`: escreve `params_type` de volta em pares `(key, value)` canônicos. |
| `hash` | fn | Retorna um hash `usize` dado um `&P`, usado para `GROUP BY`, `DISTINCT` e operações de conjunto. Opcional, mas recomendado. |
| `default` | literal de string | Uma string válida que o servidor consegue codificar na inicialização do tipo. Obrigatório, a menos que `intrinsic_default_fn` seja fornecido. |
| `intrinsic_default_fn` | fn | Calcula o padrão em vez de declará-lo: `fn(&P) -> Result<String, String>`. Use-o quando o padrão depender dos parâmetros. |

`type_name`, `max_persisted_length`, `max_decode_buffer_length`, `encode`, `decode`, `compare`, `int_to_params`, `resolve_params`, `params_type`, `params_parse` e `params_to_strings` são obrigatórios. `hash` é opcional. Dê ao tipo um padrão de uma forma ou de outra: `default` para um valor que você consegue escrever como literal, `intrinsic_default_fn` (uma função que calcula o padrão a partir de `&P`, para quando o padrão depende dos parâmetros) para um que a extensão precisa calcular. Definir ambos faz o `INSTALL EXTENSION` falhar. Não definir nenhum deixa a extensão ser instalada e, então, faz falhar a primeira instrução que nomeia o tipo.

Aqui está `padint` — um `i64` armazenado em 8 bytes fixos, com um parâmetro `width` que controla a largura de exibição preenchida com zeros:

```rust theme={null}
use villagesql::{MaybeParams, Params, Resolved};

pub struct PadintParams {
    pub width: u32,
}

pub fn padint_parse(params: Params) -> PadintParams {
    let width = params.get("width").and_then(|s| s.parse().ok()).unwrap_or(1);
    PadintParams { width }
}

pub fn padint_to_strings(p: &PadintParams) -> Vec<(String, String)> {
    vec![("width".to_string(), p.width.to_string())]
}

pub fn padint_int_to_params(n: i64) -> Result<String, String> {
    if n < 1 {
        return Err(format!("padint: width must be >= 1, got {n}"));
    }
    Ok(format!("width={n}"))
}

pub fn padint_resolve_params(params: Params) -> Result<Resolved, String> {
    let width: i64 = params
        .get("width")
        .ok_or_else(|| "padint: missing 'width' param".to_string())?
        .parse()
        .map_err(|e| format!("padint: bad width: {e}"))?;
    // Fixed 8-byte i64 storage; the decode buffer must fit the widest
    // rendering, which is the larger of an i64's digits and the padded width.
    Ok(Resolved::new(8, width.max(20)))
}

pub fn padint_encode(s: &str, params: &mut MaybeParams<PadintParams>) -> Result<Vec<u8>, String> {
    let n: i64 = s.trim().parse().map_err(|e| format!("padint: {e}"))?;
    if !params.is_known() {
        // Bare constant with no column to anchor it: infer width from digits.
        let digits = s.trim().trim_start_matches('-').len().max(1);
        params.set(PadintParams { width: u32::try_from(digits).unwrap_or(u32::MAX) });
    }
    Ok(n.to_le_bytes().to_vec())
}

pub fn padint_decode(b: &[u8], p: &PadintParams) -> Result<String, String> {
    let n = i64::from_le_bytes(b[..8].try_into().unwrap());
    Ok(format!("{n:0width$}", width = p.width as usize))
}

pub fn padint_compare(a: &[u8], b: &[u8], _p: &PadintParams) -> std::cmp::Ordering {
    i64::from_le_bytes(a[..8].try_into().unwrap()).cmp(&i64::from_le_bytes(b[..8].try_into().unwrap()))
}

villagesql::extension! {
    funcs: [],
    types: [
        villagesql::parameterized_type!(
            type_name: "padint",
            max_persisted_length: 8,
            max_decode_buffer_length: 32,
            encode: padint_encode,
            decode: padint_decode,
            compare: padint_compare,
            int_to_params: padint_int_to_params,
            resolve_params: padint_resolve_params,
            params_type: PadintParams,
            params_parse: padint_parse,
            params_to_strings: padint_to_strings,
            default: "0",
        ),
    ]
}
```

Uma função que recebe um argumento de tipo personalizado parametrizado vê
`InValue::CustomWithParams { bytes, params }` em vez do simples
`InValue::Custom(bytes)` — `params` é um `TypeParams`, uma visão somente de
leitura e sem cópia sobre os pares `key=value` declarados da coluna.

## O bloco extension! com tipos

Ao registrar tanto funções quanto tipos, o bloco `extension!` tem duas seções:

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

Uma extensão que contém apenas funções omite `types:`. Uma extensão que contém apenas tipos mantém `funcs: []` e não omite mais nada.

## Próximos passos

> [card] [Referência da API Rust](https://villagesql.com/docs/pt-BR/sdk/rust/api-reference.md)
>
> Referência para InValue, VdfReturn e as macros extension!, func!, agg\_func!, varargs\_func!, custom\_type! e custom!.

> [card] [Criando Extensões em Rust](https://villagesql.com/docs/pt-BR/extensions/build/rust-sdk.md)
>
> Primeiros passos — configuração do Cargo, primeira função, empacotamento e testes.

> [card] [Tipos Personalizados em C++](https://villagesql.com/docs/pt-BR/sdk/cpp/custom-types.md)
>
> Tipos personalizados em C++ — `make_type<>`, encode/decode/compare/hash, regras de ALTER TABLE.

> [card] [Arquitetura de Extensões](https://villagesql.com/docs/pt-BR/extensions/build/architecture.md)
>
> Como tipos personalizados são resolvidos, armazenados em cache e persistidos.


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