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

# Operações de Tipo

> Referência detalhada dos construtores de operações de tipo em C++ — encode, decode, compare, hash, padrões intrínsecos e tipos parametrizados.

Esta página é a referência dos construtores de operações de tipo em C++. Para a introdução em nível de tutorial aos tipos personalizados, consulte [Tipos Personalizados em C++](https://villagesql.com/docs/pt-BR/sdk/cpp/custom-types.md).

Os tipos personalizados exigem três operações que o mecanismo chama internamente: encode (string para binário), decode (binário para string) e compare. O hash é opcional. Implemente-as de acordo com estas assinaturas em C++ (todas disponíveis via `<villagesql/vsql.h>`):

## Tipos de Comprimento Fixo

```cpp theme={null}
// Encode: string -> binary. Write the encoded bytes via out.buffer() and
// out.set_length(n). out.set_null(), out.warning(msg), out.error(msg), or no
// call at all reports the value as invalid: ERROR 1366 in strict mode, a
// warning under IGNORE. msg is discarded.
using TypeEncodeFunc = void (*)(std::string_view from, vsql::CustomResult out);

// Decode: binary -> string. Report the result by calling out.set_length(n)
// or out.set(sv). out.set_null(), out.warning(msg), out.error(msg), or no call
// at all fails the read with ERROR 3219; msg goes to the server log, and the
// default message is "failed to decode value".
using TypeDecodeFunc = void (*)(vsql::CustomArg in, vsql::StringResult out);

// Compare: returns -1, 0, or 1 (used for ORDER BY and indexes).
using TypeCompareFunc = int (*)(vsql::CustomArg a, vsql::CustomArg b);

// Hash: returns hash code (used for GROUP BY, DISTINCT and UNION).
using TypeHashFunc = size_t (*)(vsql::CustomArg in);
```

> [!NOTE]
>
> **Alterado na versão 0.0.8-dev.** Em uma função de encode, `out.set_null()` armazena o `NULL` do SQL, e `out.error(msg)` com uma mensagem não vazia aborta a instrução com essa mensagem, qualquer que seja o modo SQL. `out.warning(msg)`, ou nenhuma chamada, continua a reportar o valor como inválido.

Omitir uma função de hash personalizada faz com que o hash recaia sobre os bytes
brutos armazenados do tipo. Isso não é seguro para um tipo cuja função de encode
possa produzir mais de uma representação binária de um mesmo valor lógico — o caso
comum é um campo de ponto flutuante, em que `-0.0` e `+0.0` são iguais na comparação
mas diferem bit a bit, de modo que `GROUP BY` e `SELECT DISTINCT` os tratariam como diferentes.

Registre essas operações usando `vsql::make_type<kTypeName>()`. O nome do tipo
é passado como um parâmetro de template que não é de tipo (NTTP) — um array
`static constexpr const char[]`. O construtor gera automaticamente nomes de VDF
no formato `TYPE::method` (por exemplo, `"MYTYPE::from_string"`) a partir desse NTTP,
de modo que nenhuma correspondência manual de strings seja necessária. Passe o objeto de
tipo construído para `.type()` no construtor da extensão; chamadas `.func()`
separadas para operações de tipo não são necessárias.

> [!WARNING]
>
> O nome do tipo deve ser uma variável `static constexpr const char[]` — um literal de string não pode ser usado como parâmetro de template que não é de tipo. Passar `"MYTYPE"` diretamente produz um erro de compilação como:
>
> ```
> error: '"MYTYPE"' is not a valid template argument for type 'const char*'
> ```
>
> Declare o nome como um array nomeado, como mostrado abaixo.

```cpp theme={null}
#include <villagesql/vsql.h>

using namespace vsql;

static constexpr const char kMyTypeName[] = "MYTYPE";

constexpr auto MYTYPE =
    vsql::make_type<kMyTypeName>()
        .persisted_length(8)
        .max_decode_buffer_length(64)
        .from_string<&my_encode>()   // auto: "MYTYPE::from_string"
        .to_string<&my_decode>()     // auto: "MYTYPE::to_string"
        .compare<&my_compare>()      // auto: "MYTYPE::compare"
        .hash<&my_hash>()            // optional, auto: "MYTYPE::hash"; set it for types that store floating-point values
        .intrinsic_default_str("0")  // string-literal intrinsic default
        .build();

VEF_GENERATE_ENTRY_POINTS(
    make_extension()
        .type(MYTYPE))
```

`build()` falha em tempo de compilação se `from_string`, `to_string` ou `compare`
estiver ausente. Cada método de template verifica a assinatura do pointer de função via
`static_assert`.

Um tipo personalizado pode declarar no máximo **65532 bytes** de armazenamento por valor. Uma
coluna personalizada é sustentada por um campo `VARBINARY`, e o orçamento de 65535 bytes por
linha do MySQL também paga pelos dois bytes de comprimento desse campo e pelo byte de nulo da
linha; como a decisão de uma coluna ser nullable é tomada por coluna em `CREATE TABLE`, e
nullable é o padrão, o limite precisa deixar espaço para o byte de nulo. Tanto
`persisted_length` quanto `max_persisted_length` são verificados contra ele, e a
verificação ocorre em `INSTALL EXTENSION` em vez de em `CREATE TABLE`, de modo que um tipo
largo demais é rejeitado junto com a extensão inteira:

```text theme={null}
ERROR 3219 (HY000): Failed to install extension 'vsql_huge_fixed_test': type 'HUGE_FIELD' declares 70000 bytes of storage per value, which exceeds the maximum of 65532 bytes supported for custom-type columns today
```

Ficar abaixo do limite não garante que uma coluna caiba. Todas as demais colunas da
tabela usam o mesmo orçamento de 65535 bytes por linha, que somente `CREATE TABLE` pode
avaliar, portanto um tipo no limite ou perto dele ainda pode falhar lá com
`ER_TOO_BIG_ROWSIZE`. Um `VARBINARY` simples da mesma largura se comporta da mesma forma.

## Padrão Intrínseco

Quando uma coluna de tipo personalizado `NOT NULL` recebe `NULL` no modo `IGNORE`
(por exemplo, `INSERT IGNORE` ou `UPDATE IGNORE`), o servidor chama o padrão
intrínseco para produzir um valor alternativo em vez de gerar um erro. O
padrão intrínseco fornece uma representação em string; o servidor a converte
para binário usando a função `from_string` do tipo.

> [!NOTE]
>
> Se você omitir tanto `.intrinsic_default_str()` quanto `.intrinsic_default_vdf()`,
> o servidor chama `from_string("")` como alternativa. Isso acontece quando o tipo
> é **usado pela primeira vez** (na criação da tabela), não em `INSTALL EXTENSION`. Se a sua
> função de encode rejeitar a string vazia, a inicialização do tipo falha com um
> erro visível no cliente SQL:
>
> ```
> ERROR 3219 (HY000): Type 'my_ext.MYTYPE' failed to initialize: from_string VDF failed to encode intrinsic default input '' (expected persisted_length=M)
> ```
>
> Se ela codificar a string vazia para o número errado de bytes, o erro é:
>
> ```
> ERROR 3219 (HY000): Type 'my_ext.MYTYPE' failed to initialize: from_string VDF encoded intrinsic default input '' to N bytes, expected persisted_length=M
> ```
>
> Para tipos de comprimento fixo, a string padrão deve codificar para exatamente
> `persisted_length` bytes. Defina um padrão explícito para qualquer tipo em que
> a string vazia não seja uma entrada válida.

Forneça o padrão intrínseco de uma de duas formas. As duas são mutuamente exclusivas:
declarar tanto `.intrinsic_default_str()` quanto `.intrinsic_default_vdf()` é um
erro de compilação com os cabeçalhos da dev ABI (`-DVSQL_USE_DEV_ABI=ON`); com os
cabeçalhos estáveis, o servidor rejeita o tipo no `INSTALL EXTENSION`.

**Literal de string: `.intrinsic_default_str()`**

Para um padrão constante, passe a string diretamente no construtor do tipo (como
mostrado no exemplo de comprimento fixo acima, com `.intrinsic_default_str("0")`).

**Baseado em VDF: `.intrinsic_default_vdf()` + `make_intrinsic_default`**

Quando o valor padrão depende dos parâmetros do tipo, implemente uma função
de acordo com uma destas assinaturas (disponíveis via `<villagesql/vsql.h>`):

```cpp theme={null}
// Fixed (no type parameters):
using IntrinsicDefaultFunc = std::string (*)(char *error_msg);

// Parameterized (receives cached parsed params):
template <typename P>
using IntrinsicDefaultWithParamsFunc = std::string (*)(const P &,
                                                       char *error_msg);
```

Retorne uma representação `std::string` do valor padrão. Em caso de erro,
escreva uma mensagem em `error_msg` e retorne qualquer valor (o SDK verifica
`error_msg[0] != '\0'` para detectar erros). Registre com
`make_intrinsic_default<&fn>("vdf_name")` (um argumento: o nome da VDF) e
referencie esse nome no construtor do tipo com `.intrinsic_default_vdf()`.
O exemplo de tipos parametrizados abaixo mostra o padrão de registro completo.

```cpp theme={null}
std::string mytype_default(const MyTypeParams &p, char * /*error_msg*/) {
  return /* build string representation based on p */;
}
```

## Tipos Parametrizados

Os tipos parametrizados precisam dos parâmetros declarados da coluna nos momentos de
encode, decode, compare e hash para determinar os tamanhos de alocação e o layout.
Defina uma struct de parâmetros com uma função de parse e uma função inversa `to_strings`,
registre ambas no construtor do tipo com
`.params<P, &ParseFunc, &ToStringsFunc>()`, e use `vsql::MaybeParams<P> &` (encode) ou
`vsql::CustomArgWith<P>` (decode, compare, hash) como o primeiro argumento das suas funções de operação de tipo. O SDK armazena em cache o resultado do parse
por combinação única de parâmetros, de modo que a função de parse é executada no máximo uma vez
por instanciação de tipo. A função `to_strings` é a inversa de `parse`:
ela escreve um `P` tipado de volta na forma canônica de string chave/valor para que
o servidor possa publicar os parâmetros inferidos no mesmo formato que `parse` consome.

O cache de parse é indexado pela própria struct de parâmetros: `TypeParamsCache<P>` é
uma única instância por `P`, compartilhada por todo tipo personalizado declarado com
aquele `P`. Se mais de um dos seus tipos usa a mesma struct de parâmetros, cada um
deles deve registrar a mesma função de parse; um tipo que precisa fazer o parse de
forma diferente precisa da sua própria struct de parâmetros.

```cpp theme={null}
struct MyTypeParams {
  int64_t dimension;
  static MyTypeParams parse(const std::map<std::string, std::string> &p) {
    return {.dimension = stoll(p.at("dimension"))};
  }
  static void to_strings(const MyTypeParams &p,
                         std::map<std::string, std::string> &out) {
    out["dimension"] = std::to_string(p.dimension);
  }
};

void mytype_encode(vsql::MaybeParams<MyTypeParams> &params,
                   std::string_view from, vsql::CustomResult out) {
  const MyTypeParams &p = params.value();  // is_known() is false when the server infers the params from a constant string
  size_t bytes = (size_t)p.dimension * 4;
  auto buf = out.buffer();
  if (buf.size() < bytes) { out.error("MYTYPE: buffer too small"); return; }
  // ... parse from, write to buf ...
  out.set_length(bytes);
}

void mytype_decode(vsql::CustomArgWith<MyTypeParams> in,
                   vsql::StringResult out) {
  const MyTypeParams &p = in.params();
  // ... read p.dimension floats from in.value(), write to out.buffer() ...
  out.set_length(bytes_written);
}

int mytype_compare(vsql::CustomArgWith<MyTypeParams> a,
                   vsql::CustomArgWith<MyTypeParams> b) {
  // Returns -1, 0, or 1.
}

size_t mytype_hash(vsql::CustomArgWith<MyTypeParams> in) {
  // Returns hash code.
}

// Converts MYTYPE(N) integer syntax to a parameter map.
// Signature: IntToTypeParamsFunc from <villagesql/vsql.h>.
bool mytype_int_to_params_fn(int64_t value,
                             std::map<std::string, std::string> &params,
                             char *error_msg) {
  if (value <= 0) {
    snprintf(error_msg, VEF_MAX_ERROR_LEN,
             "MYTYPE: dimension must be a positive integer");
    return true;
  }
  params["dimension"] = std::to_string(value);
  return false;  // success
}

// Validates parameters and computes storage sizes.
// Signature: ResolveTypeParamsFunc from <villagesql/vsql.h>.
bool mytype_resolve_params_fn(const std::map<std::string, std::string> &params,
                              vsql::ResolvedTypeParams *result,
                              char *error_msg) {
  auto it = params.find("dimension");
  if (it == params.end()) {
    snprintf(error_msg, VEF_MAX_ERROR_LEN, "MYTYPE: dimension parameter is required");
    return true;
  }
  char *endptr = nullptr;
  int64_t dim = std::strtoll(it->second.c_str(), &endptr, 10);
  if (endptr == it->second.c_str() || *endptr != '\0') {
    snprintf(error_msg, VEF_MAX_ERROR_LEN,
             "MYTYPE: invalid dimension value '%s'", it->second.c_str());
    return true;
  }
  if (dim <= 0) {
    snprintf(error_msg, VEF_MAX_ERROR_LEN,
             "MYTYPE: dimension must be a positive integer");
    return true;
  }
  if (dim > INT64_MAX / 4) {
    snprintf(error_msg, VEF_MAX_ERROR_LEN,
             "MYTYPE: dimension %" PRId64 " is too large", dim);
    return true;
  }
  result->persisted_length = dim * 4;
  result->max_decode_buffer_length = 64;
  return false;  // success
}
```

Registre `.params<>()` no construtor do tipo. Use `.int_to_params<&mytype_int_to_params_fn>()`
para tratar a sintaxe de inteiro `MYTYPE(N)` e `.resolve_params<&mytype_resolve_params_fn>()` para
validar os parâmetros e calcular os tamanhos de armazenamento. Chame `.max_persisted_length(N)` com um
limite superior do tamanho persistido em bytes em todas as parametrizações válidas; o servidor
usa isso para dimensionar o buffer de encode no caminho de inferência de parâmetros de tipo, onde ele ainda não inferiu
os parâmetros e, portanto, não pode consultar `resolve_params`, e rejeita um `persisted_length` fixo resolvido acima dele.
Para um padrão intrínseco baseado em VDF, use `.intrinsic_default_vdf()` com o nome da VDF e
registre a VDF separadamente via `make_intrinsic_default<&mytype_default>("vdf_name")`.

`build()` exige `.resolve_params<>()` sempre que `.params<>()` ou `.int_to_params<>()` estiver
registrado, quando você compila com os cabeçalhos da dev ABI (`-DVSQL_USE_DEV_ABI=ON`): `int_to_params` apenas reescreve a
forma abreviada `MYTYPE(N)` em uma string de parâmetros, enquanto `resolve_params` é o
portão que valida os parâmetros e resolve o tamanho de armazenamento. Omiti-lo é um
erro de compilação nesse caso; com os cabeçalhos estáveis, o servidor rejeita o tipo no `INSTALL EXTENSION`.

> [!WARNING]
>
> `.max_persisted_length()` requer o VEF Protocol 3 ou superior. Um tipo que o utiliza
> não pode ser carregado por servidores anteriores ao Protocol 3. Ele é válido apenas para
> tipos de comprimento variável ou parametrizados; um tipo de comprimento fixo não
> parametrizado que o define falha ao compilar com os cabeçalhos da dev ABI
> (`-DVSQL_USE_DEV_ABI=ON`), e o servidor o rejeita no `INSTALL EXTENSION` quando
> compilado com os cabeçalhos estáveis.

Os tamanhos que `resolve_params` reporta também são validados no momento do `CREATE TABLE`,
uma vez que os parâmetros de uma coluna sejam conhecidos. Para um tipo de comprimento fixo, o
`persisted_length` resolvido não pode exceder o `max_persisted_length` declarado: uma
parametrização cujo footprint calculado (por exemplo, um dimensionamento `dimension * 4`)
ultrapasse esse limite é rejeitada com um erro de sintaxe. Independentemente do tipo de
comprimento, `resolve_params` deve resolver um `max_decode_buffer_length` maior que
zero, já que esse valor dimensiona o buffer de trabalho do decode; um resultado não positivo é
rejeitado com a mesma classe de erro.

```cpp theme={null}
static constexpr const char kMyTypeName[] = "MYTYPE";

// Maximum valid dimension for MYTYPE.
constexpr int64_t kMyTypeMaxDimension = 1024;  // your max valid dimension
// Upper bound on MYTYPE's persisted byte size across all valid params.
constexpr int64_t kMyTypeMaxPersistedLength = kMyTypeMaxDimension * 4;

constexpr auto MYTYPE =
    vsql::make_type<kMyTypeName>()
        .persisted_length(-1)    // fixed length, sized per column by resolve_params
        .max_decode_buffer_length(16)
        .max_persisted_length(kMyTypeMaxPersistedLength)
        .params<MyTypeParams, &MyTypeParams::parse, &MyTypeParams::to_strings>()
        .int_to_params<&mytype_int_to_params_fn>()
        .resolve_params<&mytype_resolve_params_fn>()
        .from_string<&mytype_encode>()
        .to_string<&mytype_decode>()
        .compare<&mytype_compare>()
        .intrinsic_default_vdf("mytype_intrinsic_default")
        .build();

using namespace vsql;

VEF_GENERATE_ENTRY_POINTS(
    make_extension()
        .type(MYTYPE)
        .func(make_intrinsic_default<&mytype_default>(
            "mytype_intrinsic_default")))
```

As variantes parametrizadas (`TypeEncodeWithParamsFunc<P>`,
`TypeDecodeWithParamsFunc<P>`, `TypeCompareWithParamsFunc<P>` e
`TypeHashWithParamsFunc<P>`) juntamente com `ParamsToStringsFunc<P>`
(`void fn(const P&, std::map<std::string,std::string>&)`) estão disponíveis via
`<villagesql/vsql.h>`.
Os métodos de template `vsql::make_type` detectam o argumento de parâmetros e roteiam
através do cache de parâmetros automaticamente. As funções de encode recebem
`vsql::MaybeParams<P> &` como primeiro argumento; `is_known()` é falso quando o servidor infere
os parâmetros a partir de uma string constante, e `value()` retorna `const P&` uma vez que eles sejam conhecidos. As variantes de decode, compare e hash
recebem `vsql::CustomArgWith<P>`, cujo acessador `params()` retorna
`const P&`.

**Fornecendo parâmetros em SQL.** Duas sintaxes chegam a `resolve_params`:

* **Inteiro** — `MYTYPE(N)`. O servidor roteia `N` através de `int_to_params` para
  construir o mapa de parâmetros. Requer `.int_to_params<>()`.
* **String** — `MYTYPE('key=value,...')`. O servidor normaliza a string e
  chama `resolve_params` diretamente; `int_to_params` não é envolvido. Disponível
  sempre que `.resolve_params<>()` estiver registrado — sem chamada extra do construtor.

Um tipo que registra apenas `.resolve_params<>()` aceita a forma de string e
rejeita `MYTYPE(N)`.

`SHOW CREATE TABLE` não repete o que o autor escreveu. O servidor mantém o
mapa de parâmetros resolvido e reconstrói uma forma de exibição a partir dele, preferindo
`MYTYPE(N)` quando `int_to_params(N)` reproduz o mapa inteiro e recorrendo à
forma canônica `MYTYPE('key=value,...')` com as chaves ordenadas. Uma coluna escrita como
`MYTYPE('dimension=8')` portanto é lida de volta como `MYTYPE(8)`.

```sql theme={null}
CREATE TABLE t (v ext.MYTYPE(8));              -- integer form (.int_to_params)
CREATE TABLE t2 (v ext.MYTYPE('dimension=8')); -- string form (.resolve_params only)
```

> [!NOTE]
>
> A string de parâmetros serializada `key=value,...` que `int_to_params` produz
> e `resolve_params` consome é limitada a `VEF_MAX_TYPE_PARAMS_STRING_LEN`
> (1024 bytes). Uma parametrização cuja string canônica exceda esse limite
> é rejeitada com um erro definido em vez de ser truncada silenciosamente — mantenha os
> nomes e valores de parâmetros combinados de um único tipo dentro de 1024 bytes.

Seja qual for a sintaxe que fornece os parâmetros, o servidor valida o mapa
resultante antes de chamar `resolve_params`: toda chave deve ser não vazia,
aparecer apenas uma vez e ter um valor não vazio. Uma violação é um erro de
sintaxe que nomeia o tipo. Exceto no caso de um nome de parâmetro vazio, ele
também nomeia a chave problemática:

```sql theme={null}
-- vsql_tvector ships in the villagesql/server Docker image; with a tarball install, build it from villagesql/examples/vsql-tvector in the server source
INSTALL EXTENSION vsql_tvector;
CREATE TABLE t3 (v vsql_tvector.TVECTOR('dimension=3,dimension=4'));
-- ERROR 1064 (42000) at line 1: Type 'vsql_tvector.TVECTOR': duplicate parameter 'dimension' near 'vsql_tvector.TVECTOR('dimension=3,dimension=4'))' at line 1
```

> [!NOTE]
>
> **Alterado na versão 0.0.8-dev.** O texto dos parâmetros de tipo é convertido do conjunto de caracteres da conexão para UTF-8, e um texto que não é UTF-8 válido ou que contém um byte NUL é rejeitado. Nomes de parâmetro duplicados são comparados sem distinção de maiúsculas e minúsculas nem de acentos, portanto `café` e `cafe` são o mesmo nome.

### Reescrevendo parâmetros e fornecendo padrões

`resolve_params` tem uma segunda sobrecarga, mutável, declarada apenas nos cabeçalhos
opcionais da dev ABI (`-DVSQL_USE_DEV_ABI=ON`): ela recebe o mapa de parâmetros por
referência não constante, de modo que o tipo pode reescrevê-lo — normalmente para preencher padrões que o
autor omitiu. Registre-a da mesma forma (`.resolve_params<&fn>()` aceita
qualquer forma; registre apenas uma):

```cpp theme={null}
bool mytype_resolve_params_fn(std::map<std::string, std::string> &params,
                              vsql::ResolvedTypeParams *result, char *error_msg) {
  if (params.find("dimension") == params.end())
    params["dimension"] = "128";                 // supply a default
  const std::string &dim_str = params.at("dimension");
  char *endptr = nullptr;
  int64_t dim = std::strtoll(dim_str.c_str(), &endptr, 10);
  if (endptr == dim_str.c_str() || *endptr != '\0') {
    snprintf(error_msg, VEF_MAX_ERROR_LEN,
             "MYTYPE: invalid dimension value '%s'", dim_str.c_str());
    return true;
  }
  if (dim <= 0) {
    snprintf(error_msg, VEF_MAX_ERROR_LEN,
             "MYTYPE: dimension must be a positive integer");
    return true;
  }
  if (dim > INT64_MAX / 4) {
    snprintf(error_msg, VEF_MAX_ERROR_LEN,
             "MYTYPE: dimension %" PRId64 " is too large", dim);
    return true;
  }
  result->persisted_length = dim * 4;
  result->max_decode_buffer_length = 64;
  return false;                                  // success
}
```

Um callback `resolve_params` mutável pode adicionar ou alterar parâmetros, mas
remover um que o chamador escreveu é um erro: `Type '<extension>.<type>':
resolve_params removed parameter '<key>'; parameters may be added or changed
but not removed`.

O mapa reescrito torna-se a string de parâmetros canônica que o servidor persiste e
`SHOW CREATE TABLE` imprime, portanto a reescrita deve ser idempotente. Uma declaração
**simples** (`MYTYPE`, sem comprimento ou parâmetros) chama `resolve_params` com um
mapa vazio quando o tipo não registra `.int_to_params<>()`, de modo que um tipo que fornece padrões dá a cada
coluna parâmetros explícitos — o `BITFIELD` de `vsql_bitfield_test` resolve uma coluna
simples para `max_number_of_bits=4096`:

```sql theme={null}
-- vsql_bitfield_test is built from villagesql/test-extensions/vsql-bitfield-test in the server source; the release does not ship it
INSTALL EXTENSION vsql_bitfield_test;
CREATE TABLE bits (id INT PRIMARY KEY, b vsql_bitfield_test.BITFIELD);
SHOW CREATE TABLE bits;   -- b persists as BITFIELD('max_number_of_bits=4096')
```

## Tipos de Comprimento Variável

Um tipo personalizado de comprimento variável decide seu tamanho persistido por valor, em vez de
usar um único footprint fixo. Declare um chamando `.variable_length_type()`
no construtor do tipo, o que define a flag `variable_length` do tipo.

> [!WARNING]
>
> `.variable_length_type()` eleva o protocol exigido pelo tipo para o VEF Protocol 4.
> O servidor lê a flag `variable_length` apenas no protocol 4 ou superior. Compile
> com os cabeçalhos opcionais da dev ABI (`-DVSQL_USE_DEV_ABI=ON`); servidores mais antigos não
> leem a flag.

Os tipos de comprimento variável também devem chamar `.max_persisted_length(N)`. `build()` falha
em tempo de compilação se ele for omitido — o servidor precisa do limite superior para alocar
um buffer para o campo de suporte. Um tipo de comprimento variável não deve também chamar
`.persisted_length()`: seu tamanho é decidido por valor, portanto `build()` rejeita a
combinação em tempo de compilação.

`.variable_length_type()` é monotônico: chamá-lo antes ou depois dos setters do Protocol 3
(`max_persisted_length()`, `params()`, `int_to_params()`) não reduz
a exigência de protocol de volta para abaixo do Protocol 4.

```cpp theme={null}
#include <villagesql/vsql.h>
using namespace vsql;

static constexpr const char kMyTypeName[] = "MYTYPE";
constexpr int64_t kMyTypeMaxPersistedLength = 4096;

constexpr auto MYTYPE =
    vsql::make_type<kMyTypeName>()
        .variable_length_type()  // per-value sizing; requires Protocol 4
        .max_persisted_length(kMyTypeMaxPersistedLength)
        .max_decode_buffer_length(64)
        .from_string<&my_encode>()
        .to_string<&my_decode>()
        .compare<&my_compare>()
        .build();

VEF_GENERATE_ENTRY_POINTS(
    make_extension()
        .type(MYTYPE))
```

> [!NOTE]
>
> Como todo tipo personalizado, um tipo de comprimento variável deve produzir um
> [padrão intrínseco](#intrinsic-default) utilizável. O padrão é codificado dentro da
> capacidade máxima do campo, e qualquer resultado não vazio de 1 a
> `max_persisted_length` bytes é aceito. Um tipo cujo encode de string vazia
> produz **zero** bytes (um array ou conjunto de bits vazio, digamos) não tem um padrão
> utilizável, portanto declare um explícito que codifique para um valor não vazio:
>
> ```cpp theme={null}
>         .max_persisted_length(kMyTypeMaxPersistedLength)
>         .intrinsic_default_str("[0]")  // empty "[]" would encode to zero bytes
> ```
>
> Caso contrário, o tipo falha ao inicializar na primeira vez que uma coluna o
> referencia, em `CREATE TABLE` — o mesmo que um tipo de comprimento fixo que não consegue
> codificar `from_string("")`.

## Tipos Personalizados em Stored Procedures

Tipos personalizados de extensão podem ser usados como tipos de parâmetro de stored procedure e
em declarações de variáveis `DECLARE`. O servidor resolve o tipo personalizado no
momento da execução da rotina usando os metadados de tipo da extensão instalada.

```sql theme={null}
DELIMITER //
CREATE PROCEDURE insert_complex(IN val COMPLEX)
BEGIN
  DECLARE tmp COMPLEX;
  SET tmp = val;
  INSERT INTO t1 VALUES (tmp);
END //
DELIMITER ;
```

## Veja Também

* [Tipos Personalizados em C++](https://villagesql.com/docs/pt-BR/sdk/cpp/custom-types.md) — introdução em nível de tutorial aos tipos personalizados
* [Referência da API C++](https://villagesql.com/docs/pt-BR/sdk/cpp/extension-api-reference.md) — contratos de VDF, tratamento de null e dimensionamento de buffer
* [Desenvolvimento em C++](https://villagesql.com/docs/pt-BR/sdk/cpp/development.md) — profundidade de autoria de VDF, tipos de argumento e resultado, agregados, varargs


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