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

# Desenvolvimento em C++

> Profundidade na criação de VDFs para extensões C++ — tipos de argumento e resultado, segurança de exceções, agregações, prerun/postrun, varargs e registro.

Este guia é a referência aprofundada para escrever implementações de VDF em C++. Ele é o complemento de [Criando Extensões em C++](https://villagesql.com/docs/pt-BR/extensions/build/create.md), que cobre as etapas de compilação de ponta a ponta, e de [Testes em C++](https://villagesql.com/docs/pt-BR/sdk/cpp/testing.md), que cobre o ciclo de teste e iteração.

> [!WARNING]
>
> O Protocol 3 do VEF é estável desde a v0.0.4. O Protocol 4 está em desenvolvimento e está disponível apenas por meio de cabeçalhos de ABI de desenvolvimento opcionais (`-DVSQL_USE_DEV_ABI=ON`). Extensões compiladas com o antigo Protocol 2 são rejeitadas pelo servidor e precisam ser recompiladas.

## Escrevendo Funções de Extensão

As funções de extensão são escritas em C++ e registradas com o VEF. Inclua um
único cabeçalho para acessar as APIs de VDF, de tipo e do construtor de extensão:

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

## Segurança de Exceções

Conter as exceções é responsabilidade da extensão. O SDK não envolve a sua
implementação em um `try`/`catch`, e o servidor também não, então uma exceção
que escapa de um ponto de entrada desenrola a pilha através da fronteira da ABI C
sem nenhum tratador acima dela e o processo do servidor é encerrado. Uma entrada
SQL comum chega a isso: `std::stoi()` lança uma exceção em uma string não numérica, e
`nlohmann::json::dump()` lança uma exceção em um texto que não é UTF-8 válido.

> [!NOTE]
>
> **Alterado na versão 0.0.8-dev.** Na dev ABI (Protocolo 4, `-DVSQL_USE_DEV_ABI=ON`), o SDK captura uma exceção que escapa do corpo de uma VDF ou da função accumulate ou result de uma agregação. A instrução falha com um erro de VDF em vez de o servidor ser encerrado. A ABI estável não muda.

Envolva o corpo de cada ponto de entrada que você registrar: VDFs, funções de
agregação, ganchos prerun e postrun, e as operações de um tipo personalizado.

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

using namespace vsql;

void to_int_impl(StringArg in, IntResult out) {
  if (in.is_null()) { out.set_null(); return; }
  try {
    out.set(std::stoi(std::string(in.value())));  // throws on bad input
  } catch (const std::exception &e) {
    out.warning(e.what());
  } catch (...) {
    out.warning("unexpected error");
  }
}
```

Capture `...` além de `const std::exception &`: uma dependência pode lançar um
tipo que não deriva de `std::exception`, e esse ainda encerra o servidor.
Escolha entre `warning()` e `error()` com o mesmo critério de qualquer
outra falha — `warning()` para entrada inválida à qual a instrução deve sobreviver,
`error()` para os casos em que continuar é inseguro.

## Tipos de Argumento e Resultado

Os parâmetros e resultados de VDF são passados como tipos de argumento e resultado com segurança de tipo.
O framework os detecta a partir da assinatura da sua função e se adapta
automaticamente — a sintaxe de registro `make_func` permanece inalterada.

**Tipos de argumento:** `IntArg`, `RealArg`, `StringArg`, `CustomArg` — cada um
fornece `is_null()` e `value()`. Para tipos personalizados parametrizados,
`CustomArgWith<P>` adiciona um acessador `params()` que retorna a struct de params
analisada e armazenada em cache (consulte [Tipos Parametrizados](https://villagesql.com/docs/pt-BR/sdk/cpp/type-operations.md#parameterized-types)).

**Tipos de resultado:** `IntResult`, `RealResult`, `StringResult`, `CustomResult`
— cada um fornece `set_null()`, `warning(msg)` e `error(msg)`. Os resultados
escalares também fornecem `set(value)`. Os resultados de buffer fornecem `buffer()` e
`set_length(len)`. `StringResult` também fornece
`set(std::string_view)`, que copia até `buffer().size()` bytes da
view e define o comprimento em uma única chamada. Para tipos personalizados parametrizados,
`CustomResultWith<P>` adiciona um acessador `params()`.

**Tipo Span:** `value()` e `buffer()` nos tipos de argumento e resultado
orientados a bytes retornam um `vsql::Span<T>` — uma view não proprietária sobre uma sequência
contígua de `T` com `data()`, `size()`, `empty()`, `begin()`/`end()` e
`operator[]`. No C++20 ele é um alias para `std::span<T>`; no C++17, este
SDK fornece uma implementação mínima compatível, de modo que o mesmo código compila em
qualquer um dos padrões. Ele está disponível através de `<villagesql/vsql.h>`.

`warning(msg)` retorna SQL NULL para a linha e anexa um aviso SQL. Esse NULL é um valor comum do ponto de vista do servidor, portanto só se torna um erro de instrução da mesma forma que qualquer NULL: gravá-lo em uma coluna `NOT NULL` falha sob `STRICT_TRANS_TABLES`, assim como `INSERT INTO t VALUES (NULL)` falharia. Gravá-lo em uma coluna que aceita NULL tem sucesso, com o aviso incluído. Use `warning(msg)` para entrada inválida recuperável, como uma string que não pode ser analisada em uma função de codificação. Use `error(msg)` para dados armazenados corrompidos ou qualquer condição em que continuar seja inseguro. A mensagem de ambos é truncada para caber no buffer de erro interno do servidor, se necessário.

**Exemplo escalar** — soma dois inteiros:

```cpp theme={null}
using namespace vsql;

void add_impl(IntArg a, IntArg b, IntResult out) {
  if (a.is_null() || b.is_null()) { out.set_null(); return; }
  out.set(a.value() + b.value());
}

// Registration is unchanged:
// make_func<&add_impl>("add").returns(INT).param(INT).param(INT).build()
```

**Exemplo binário** — transforma um valor de tipo personalizado no buffer de resultado:

```cpp theme={null}
using namespace vsql;

void rot13_impl(CustomArg in, CustomResult out) {
  if (in.is_null()) { out.set_null(); return; }
  auto src = in.value();   // vsql::Span<const unsigned char>
  auto dst = out.buffer(); // vsql::Span<unsigned char>
  for (size_t i = 0; i < src.size(); i++) { dst[i] = transform(src[i]); }
  out.set_length(src.size());
}
```

Para `StringResult` e `CustomResult`, escreva em `buffer()` e, em seguida, chame
`set_length()` com o número de bytes escritos. `buffer().size()` é a
capacidade máxima.

Para VDFs que retornam um tipo personalizado (`.returns(MYTYPE)`), o servidor
dimensiona o buffer de resultado para o `persisted_length` do tipo de retorno resolvido
automaticamente — os autores de extensões não precisam declarar `.buffer_size(...)`
no construtor da função nesse caso. Se `prerun` aumentar ainda mais o buffer,
esse tamanho maior é preservado. É isso que permite, por exemplo, que
`SVECTOR::from_string('[…1024 floats…]')` codifique um vetor amplo sem que o
buffer de resultado fique sem espaço.

Você pode usar estilos diferentes entre funções na mesma extensão — o estilo de cada
função é determinado por sua própria assinatura.

## VDFs Agregadas

As VDFs agregadas acumulam estado entre linhas dentro de cada grupo `GROUP BY` e
retornam um único resultado por grupo, como `SUM` ou `COUNT` do SQL. Use
`make_aggregate_func<State, &result_fn>("name")` para registrar uma. O tipo State
é o buffer de acumulação por grupo; `prerun` e `postrun` são
gerados automaticamente para alocá-lo e removê-lo.

A função de resultado deve ter a assinatura `void(const State&, ResultType)`
onde `ResultType` é um dentre `IntResult`, `RealResult`, `StringResult`,
`CustomResult` ou `CustomResultWith<P>`. Chame `out.set(value)` para retornar um
valor ou `out.set_null()` para retornar SQL NULL.

Tanto `.clear<>()` quanto `.accumulate<>()` são obrigatórios. O construtor impõe
isso em tempo de compilação (via `build()`), e o servidor valida novamente no
momento do `INSTALL EXTENSION` — `clear` redefine o estado, `accumulate` acumula as linhas e
a função de resultado lê o estado final.

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

using namespace vsql;

// State type: nullopt means no non-NULL rows seen yet.
using SumState = std::optional<long long>;

void my_clear(SumState &s) { s = std::nullopt; }
void my_acc(SumState &s, IntArg v) {
  if (!v.is_null()) s = s.value_or(0) + v.value();
}
void my_result(const SumState &s, IntResult out) {
  if (!s.has_value()) { out.set_null(); return; }
  out.set(s.value());
}

// Registration:
// make_aggregate_func<SumState, &my_result>("my_sum")
//     .returns(INT)
//     .param(INT)
//     .clear<&my_clear>()
//     .accumulate<&my_acc>()
//     .build()
```

Como funcionam os métodos do construtor:

* `make_aggregate_func<State, &result_fn>()` gera automaticamente `prerun` e `postrun` (inicializa por valor e remove o `State`).
* `.clear<&fn>()` registra sua função de redefinição `void(State&)`.
* `.accumulate<&fn>()` registra sua função de acumulação `void(State&, TypedArgs...)`. Os `TypedArgs` são deduzidos da assinatura da função (`IntArg`, `StringArg`, etc.). Chame `.accumulate<>()` depois de todas as chamadas `.param(TYPE)`: o construtor verifica a contagem de argumentos tipados contra a contagem de parâmetros SQL declarados em tempo de compilação, então chamá-lo antes falha na compilação.
* O tipo de resultado (`IntResult`, `RealResult`, etc.) é deduzido da assinatura da função de resultado.

Para um contador que nunca retorna NULL, use um tipo de estado simples:

```cpp theme={null}
using CountState = long long;
void count_clear(CountState &s) { s = 0; }
void count_acc(CountState &s, IntArg v) { if (!v.is_null()) s++; }
void count_result(const CountState &s, IntResult out) { out.set(s); }
```

Uma VDF agregada com `StringResult` retorna texto: o resultado informa o
charset e a collation `utf8mb4_bin`, de modo que os clientes o exibem como caracteres
em vez de hexadecimal — o mesmo que o caminho STRING da VDF escalar. Ela também respeita
`.max_result_length(n)` — o que exige os cabeçalhos de ABI de desenvolvimento
opcionais (`-DVSQL_USE_DEV_ABI=ON`) e o Protocol 4 — da mesma forma, dimensionando um resultado agregado materializado
(uma tabela temporária de `GROUP BY`/`DISTINCT`, `CREATE TABLE ... SELECT` ou UNION) de modo que ele
não seja truncado na largura do argumento. Consulte
[Tamanhos de Buffer Personalizados](https://villagesql.com/docs/pt-BR/extensions/build/create.md#custom-buffer-sizes) para as
regras de dimensionamento e o limite.

## Estado por Instrução (Prerun e Postrun)

Algumas VDFs precisam de estado que abranja cada linha que uma única consulta toca — um contador de
chamadas, um resultado em cache, um recurso aberto. Aloque-o em um gancho **prerun**,
acesse-o a partir do corpo da VDF e libere-o em um gancho **postrun**. Ambos os ganchos
são executados uma vez por instrução; o corpo da VDF é executado uma vez por linha.

> [!NOTE]
>
> **Alterado na versão 0.0.8-dev.** O postrun também é executado quando a instrução falha antes da primeira linha, desde que o prerun tenha sido bem-sucedido.

Registre-os com `.prerun<&Hook>()` e `.postrun<&Hook>()`. As assinaturas
obrigatórias são:

| Gancho | Assinatura obrigatória |
| - | - |
| Prerun | `void(vsql::PrerunArgs, vsql::PrerunResult)` |
| Postrun | `void(vsql::PostrunArgs)` |

Use `PrerunResult::set_user_data(void*)` para armazenar o estado; use `PostrunArgs::delete_state<T>()` para liberá-lo. Se `prerun` chamar `set_user_data(new T{})`, `postrun` **deve** chamar `delete_state<T>()` — o SDK não libera automaticamente.

`PrerunArgs::type_at(i)` expõe o tipo SQL declarado de cada argumento antes que qualquer linha seja lida; os predicados `is_int()`, `is_real()`, `is_str()`, `is_custom()` no `PrerunArgType` retornado espelham os tipos de coluna. Use isto em prerun para validar os tipos de argumento ou chame `PrerunResult::request_buffer_size(n)` para dimensionar o buffer de resultado.

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

struct CallCounter { long long n = 0; };

void ba_call_index_prerun(PrerunArgs, PrerunResult out) {
  out.set_user_data(new CallCounter{});
}

void ba_call_index(CallCounter &state, IntResult out) {
  state.n++;
  out.set(state.n);
}

void ba_call_index_postrun(PostrunArgs args) {
  args.delete_state<CallCounter>();
}

// Registration:
// make_func<&ba_call_index>("ba_call_index")
//     .returns(INT).no_params()
//     .prerun<&ba_call_index_prerun>()
//     .postrun<&ba_call_index_postrun>()
//     .build()
```

## VDFs com Varargs

Uma VDF com **varargs** aceita qualquer número de argumentos de qualquer tipo SQL. Declare uma
com `.varargs()` no construtor da função, o que é mutuamente exclusivo com
`.no_params()` e `.param(TYPE)`. O corpo recebe um argumento `vsql::VarArgs`
em vez dos tipos de argumento usuais de aridade fixa.

> [!WARNING]
>
> O registro de varargs requer o Protocol 3 do VEF. Servidores mais antigos rejeitam a
> extensão no momento da instalação.

O framework não pode validar a contagem ou os tipos de argumento para VDFs com varargs. Combine
cada registro de varargs com um gancho prerun que chame `PrerunResult::error()`
em entrada inválida ou `PrerunResult::request_buffer_size(n)` para dimensionar o buffer de
resultado.

Itere sobre os argumentos com range-for. Cada elemento `AnyArg` requer uma verificação de
tipo antes de ler seu valor:

| Predicado | Acessador | Tipo de retorno |
| - | - | - |
| `is_int()` | `as_int()` | `long long` |
| `is_real()` | `as_real()` | `double` |
| `is_str()` | `as_str()` | `std::string_view` |
| `is_custom()` | `as_custom()` | `vsql::Span<const unsigned char>` |

Verifique `is_null()` antes de qualquer acessador — todos os quatro são indefinidos em um argumento nulo.

`PrerunArgType::custom_type()` retorna o nome do tipo personalizado do argumento
como um `std::string_view` — vazio para argumentos que não são de tipo
personalizado. `is_custom()` sozinho aceita todo tipo personalizado registrado no
servidor; combiná-lo com uma comparação de `custom_type()` restringe uma chamada
varargs a um tipo específico:

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

// POINT2D stores two little-endian int32 (x, y) in 8 bytes — registration
// omitted here; see Custom Types for make_type<>().

void point_path_prerun(PrerunArgs args, PrerunResult out) {
  if (args.size() == 0) {
    out.error("point_path requires at least one point");
    return;
  }
  for (size_t i = 0; i < args.size(); i++) {
    auto t = args.type_at(i);
    if (!t.is_custom() || t.custom_type() != "POINT2D") {
      out.error("point_path: every argument must be a POINT2D");
      return;
    }
  }
  out.request_buffer_size(16 + args.size() * 32);
}

void point_path(VarArgs args, StringResult out) {
  auto buf = out.buffer();
  size_t off = 0;
  bool first = true;
  for (auto a : args) {
    if (a.is_null()) { out.set_null(); return; }
    auto bytes = a.as_custom();
    int x, y;
    memcpy(&x, bytes.data(), 4);
    memcpy(&y, bytes.data() + 4, 4);
    if (!first) {
      memcpy(buf.data() + off, " -> ", 4);
      off += 4;
    }
    first = false;
    off += static_cast<size_t>(
        snprintf(buf.data() + off, buf.size() - off, "(%d,%d)", x, y));
  }
  out.set_length(off);
}

// Registration:
// make_func<&point_path>("point_path")
//     .returns(STRING).varargs()
//     .prerun<&point_path_prerun>()
//     .build()
```

```sql theme={null}
SELECT point_path(POINT2D::from_string('0,0'), POINT2D::from_string('1,2'));
-- (0,0) -> (1,2)
SELECT point_path('1,2');
-- ERROR 1123 (HY000): Can't initialize function 'point_path'; point_path: every argument must be a POINT2D
```

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

constexpr size_t kBytearrayLen = 8;

void ba_concat_all_prerun(PrerunArgs args, PrerunResult out) {
  if (args.size() == 0) {
    out.error("ba_concat_all requires at least one argument");
    return;
  }
  for (size_t i = 0; i < args.size(); i++) {
    auto t = args.type_at(i);
    if (!t.is_custom() && !t.is_str()) {
      out.error("ba_concat_all: argument " + std::to_string(i) +
                " must be BYTEARRAY");
      return;
    }
  }
  out.request_buffer_size(args.size() * kBytearrayLen);
}

void ba_concat_all(VarArgs args, StringResult out) {
  auto dst = out.buffer();
  size_t off = 0;
  for (auto a : args) {
    if (a.is_null() || !a.is_custom()) { out.set_null(); return; }
    auto bytes = a.as_custom();
    if (off + bytes.size() > dst.size()) { out.error("ba_concat_all: result too long"); return; }
    std::memcpy(dst.data() + off, bytes.data(), bytes.size());
    off += bytes.size();
  }
  out.set_length(off);
}

// Registration:
// make_func<&ba_concat_all>("ba_concat_all")
//     .returns(STRING).varargs()
//     .prerun<&ba_concat_all_prerun>()
//     .build()
```

## Decidindo os Parâmetros de Tipo de uma Função no Momento da Resolução

As regras internas do servidor normalmente determinam os parâmetros de um tipo de
retorno a partir dos tipos declarados dos seus argumentos. Às vezes elas não
conseguem. Por exemplo, `tvector_concat(TVECTOR(M), TVECTOR(N))` retorna um
`TVECTOR(M+N)`. As regras internas rejeitam dois argumentos `TVECTOR` cujas
dimensões diferem, e elas podem copiar os parâmetros de um argumento para o tipo
de retorno, mas nunca somá-los. Para casos como este, registre um **gancho
bind-and-check-types** com `.bind_and_check_types<&Hook>()` no construtor da
função, com a assinatura:

```cpp theme={null}
void my_hook(vsql::BindArgs args, vsql::BindResult out);
```

`.bind_and_check_types()` e `.varargs()` são mutuamente exclusivos na mesma
função — uma VDF com varargs não tem uma lista fixa de argumentos sobre a qual o
gancho possa raciocinar. Declarar `.varargs()` antes de
`.bind_and_check_types<&Hook>()` na cadeia do construtor é uma falha de
`static_assert` em tempo de compilação. Declará-los na outra ordem compila, mas
a extensão é rejeitada quando o servidor a carrega: o registro percorre o estado
final construído de cada função e rejeita a primeira que é varargs e tem um
gancho de bind ao mesmo tempo, com:

```text theme={null}
ERROR 3219 (HY000): Failed to load VEF extension '<name>': vef_register returned an error: VDF '<function>' declares both .varargs() and .bind_and_check_types(). This is not currently supported.
```

> [!WARNING]
>
> `.bind_and_check_types<&Hook>()` e `<villagesql/vsql/bind_check_types.h>`
> são declarados apenas nos cabeçalhos de ABI de desenvolvimento opcionais
> (`-DVSQL_USE_DEV_ABI=ON`) e exigem o Protocol 4 do VEF. Uma compilação padrão
> com a ABI estável não tem nenhum dos dois, então registrar um gancho falha na
> compilação.

O servidor chama o gancho entre duas passagens que ele faz sobre os argumentos de
uma função: a primeira passagem verifica o tipo base de cada argumento, o gancho
é executado em seguida, e a segunda passagem aplica a resposta do gancho para
cada argumento em que ele deu uma. Com um gancho instalado, a primeira passagem
não coleta parâmetros compartilhados entre argumentos do mesmo tipo
personalizado, então nenhum deles precisa concordar com os outros. Um argumento
cujo próprio tipo ainda não está resolvido, como um literal de string simples,
recebe seus parâmetros apenas do gancho. Os parâmetros de um tipo de retorno
parametrizado também vêm do gancho, no lugar da regra interna que os copia de um
argumento.

A extensão de exemplo [`vsql_tvector`](https://github.com/villagesql/villagesql-server/blob/main/villagesql/examples/vsql-tvector/src/tvector.cc)
na árvore de código-fonte do servidor registra um gancho desses em
`tvector_concat`:

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

// Excerpt from villagesql/examples/vsql-tvector/src/tvector.cc. That file
// also has the standard library includes, the constants kDefaultBytesPerElem
// (4) and kTVectorMaxDimension (4096), the TVECTOR type, the tvector_concat
// function body, and the helpers common_element_width() and
// bind_params_for_vector(), which read an argument's resolved params or count
// the elements of a constant literal.

struct TVectorParams {
  int64_t dimension;
  size_t bytes_per_elem;  // 4 for float, 8 for double

  static TVectorParams parse(const std::map<std::string, std::string> &params) {
    auto dim_it = params.find("dimension");
    int64_t dim = strtoll(dim_it->second.c_str(), nullptr, 10);
    size_t bytes = kDefaultBytesPerElem;
    auto type_it = params.find("type");
    if (type_it != params.end() && type_it->second == "double") bytes = 8;
    return TVectorParams{.dimension = dim, .bytes_per_elem = bytes};
  }

  // Inverse of parse: render a typed TVectorParams back into the canonical
  // key/value string form. Used by the SDK when the server needs to publish
  // inferred params (e.g., from a constant-string from_string) back in the
  // same shape that parse() consumes.
  static void to_strings(const TVectorParams &p,
                         std::map<std::string, std::string> &out) {
    out["dimension"] = std::to_string(p.dimension);
    out["type"] = (p.bytes_per_elem == 8) ? "double" : "float";
  }
};

// bind_and_check_types for tvector_concat.
//
// Two jobs the built-in rules cannot do:
//   1. permit arguments whose dimensions differ (parameter sharing rejects it);
//   2. compute the return dimension as M+N (the server return rule only
//      copies).
//
// A third job appears when an argument is a constant: a bare '[1,2,3]' has no
// type context, and with a hook installed nothing donates params to it, so the
// hook counts the elements itself and answers with set_arg(). The element type
// comes from whichever side the server already resolved, which is what the
// built-in rules would have donated.
void tvector_concat_bind(vsql::BindArgs args, vsql::BindResult out) {
  if (args.size() != 2) {
    out.error("expects 2 arguments");
    return;
  }

  std::string error_msg;
  size_t bpe = 0;
  if (common_element_width(args, bpe, error_msg)) {
    out.error(error_msg);
    return;
  }

  TVectorParams first{}, second{};
  bool publish_first = false, publish_second = false;

  if (bind_params_for_vector(args, 0, bpe, first, publish_first, error_msg) ||
      bind_params_for_vector(args, 1, bpe, second, publish_second, error_msg)) {
    out.error(error_msg);
    return;
  }

  // Hand back the params of whichever argument the server could not resolve.
  if (publish_first) out.set_arg(0, first);
  if (publish_second) out.set_arg(1, second);

  const int64_t total = first.dimension + second.dimension;
  if (total > kTVectorMaxDimension) {
    out.error("combined dimension " + std::to_string(total) +
              " exceeds the maximum of " +
              std::to_string(kTVectorMaxDimension));
    return;
  }
  out.set_return(TVectorParams{.dimension = total, .bytes_per_elem = bpe});
}

// Registration, inside make_extension():
// .func(make_func<&tvector_concat>("tvector_concat")
//     .returns(TVECTOR)
//     .param(TVECTOR)
//     .param(TVECTOR)
//     .bind_and_check_types<&tvector_concat_bind>()
//     .deterministic()
//     .build())
```

O gancho permite que dois vetores de dimensões diferentes sejam combinados, e ele
conta os elementos de um literal constante. Uma compilação do servidor a partir
do código-fonte inclui o `vsql_tvector`; os tarballs de release não.

```sql theme={null}
INSTALL EXTENSION vsql_tvector;
CREATE TABLE v2 (v vsql_tvector.TVECTOR(2));
CREATE TABLE v3 (v vsql_tvector.TVECTOR(3));
INSERT INTO v2 VALUES ('[1,2]');
INSERT INTO v3 VALUES ('[3,4,5]');

SELECT vsql_tvector.tvector_concat(v2.v, v3.v) AS c,
       vsql_tvector.tvector_dim(vsql_tvector.tvector_concat(v2.v, v3.v)) AS dim
FROM v2, v3;
```

```text theme={null}
+-------------+------+
| c           | dim  |
+-------------+------+
| [1,2,3,4,5] |    5 |
+-------------+------+
```

```sql theme={null}
SELECT vsql_tvector.tvector_concat(v, '[6,7]') AS c FROM v3;
```

```text theme={null}
+-------------+
| c           |
+-------------+
| [3,4,5,6,7] |
+-------------+
```

## Ganchos de Carregamento e Descarregamento da Extensão

Às vezes uma extensão tem trabalho a fazer uma vez por carregamento, em vez de uma
vez por chamada — escolher uma implementação específica de CPU, ler a sua própria
configuração uma única vez ou construir uma tabela de consulta que as VDFs depois
leem. Registre esse trabalho com `.on_init<&Fn>()` no construtor da extensão, e a
desmontagem correspondente com `.on_deinit<&Fn>()`. Ambos recebem uma função com
a assinatura `void()`, e precisam ser registrados juntos — declarar um sem o
outro falha na compilação. Uma extensão que chega ao servidor com um par
incompleto por algum outro caminho é rejeitada no carregamento:

```text theme={null}
ERROR 3219 (HY000): Failed to load VEF extension '<name>': extension registers an on_init hook without an on_deinit
```

> [!WARNING]
>
> `.on_init<&Fn>()` e `.on_deinit<&Fn>()` são declarados apenas nos cabeçalhos de ABI
> de desenvolvimento opcionais (`-DVSQL_USE_DEV_ABI=ON`). Uma compilação padrão com a
> ABI estável não tem nenhum dos dois métodos, então registrar um gancho falha na compilação.

`on_init` é executado uma vez por carregamento — no `INSTALL EXTENSION`, e
novamente a cada inicialização do servidor — depois que o servidor validou a
extensão e populou as capabilities exigidas por ela, de modo que uma variável de
sistema registrada já contém o seu valor configurado. Na inicialização,
especificamente, `on_init` é adiado até que os valores persistidos das variáveis
de sistema tenham sido aplicados, então ele nunca vê um padrão compilado no lugar
de um valor definido com `SET PERSIST`. Ele nunca é executado para uma extensão
que o servidor rejeitou. `on_deinit` é executado no descarregamento —
`UNINSTALL EXTENSION` ou desligamento do servidor — antes que o servidor
despopule essas mesmas capabilities, então ele vê o mesmo estado ativo que
`on_init` viu.

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

static float *g_sqrt_table = nullptr;

void build_table() {
  g_sqrt_table = new float[256];
  for (int i = 0; i < 256; i++) g_sqrt_table[i] = std::sqrt(float(i));
}

void free_table() {
  delete[] g_sqrt_table;
  g_sqrt_table = nullptr;
}

void fast_sqrt(IntArg n, RealResult out) {
  if (n.is_null() || n.value() < 0 || n.value() > 255) {
    out.set_null();
    return;
  }
  out.set(g_sqrt_table[n.value()]);
}

VEF_GENERATE_ENTRY_POINTS(
    make_extension()
        .on_init<&build_table>()
        .on_deinit<&free_table>()
        .func(make_func<&fast_sqrt>("fast_sqrt")
                  .returns(REAL)
                  .param(INT)
                  .build()))
```

Depois de `INSTALL EXTENSION my_extension`, `SELECT fast_sqrt(16)` retorna `4` —
um resultado que só é alcançável se `build_table` já tiver sido executado.

## VEF\_GENERATE\_REGISTRATION

`VEF_GENERATE_REGISTRATION` cria um auxiliar interno `_vef_do_register()`
que realiza o registro da extensão, mas não define os pontos de entrada
`extern "C"`. Use-o quando precisar personalizar o comportamento de `vef_register` — por
exemplo, para corrigir descritores após o registro em uma compilação de teste. Para extensões
normais, use `VEF_GENERATE_ENTRY_POINTS` em vez disso.

```cpp theme={null}
VEF_GENERATE_REGISTRATION(
    make_extension()
        .func(make_func<&my_impl>("my_func").returns(INT).no_params().build()))

// Then define your own extern "C" vef_register/vef_unregister that call
// _vef_do_register() and optionally modify the result.
```

## Operações de Tipos Personalizados

Para a referência completa do construtor de operações de tipo — codificação, decodificação, comparação, hash, padrões intrínsecos e tipos parametrizados — consulte [Operações de Tipo](https://villagesql.com/docs/pt-BR/sdk/cpp/type-operations.md).

## Capabilities Preview

As seguintes capabilities do VEF estão disponíveis como cabeçalhos Preview opcionais. A ABI e a API ainda estão em desenvolvimento ativo; consulte [Capabilities Preview](https://villagesql.com/docs/pt-BR/extensions/build/preview-capabilities.md) para a referência completa.

* **Variáveis de sistema de extensão** — [Capabilities Preview → Variáveis de Sistema](https://villagesql.com/docs/pt-BR/extensions/build/preview-capabilities.md#system-variables)
* **Variáveis de status de extensão** — [Capabilities Preview → Variáveis de Status](https://villagesql.com/docs/pt-BR/extensions/build/preview-capabilities.md#status-variables)
* **Acesso ao keyring** — [Capabilities Preview → Acesso ao Keyring](https://villagesql.com/docs/pt-BR/extensions/build/preview-capabilities.md#keyring-access)
* **Armazenamento de coluna** — [Capabilities Preview → Armazenamento de Coluna](https://villagesql.com/docs/pt-BR/extensions/build/preview-capabilities.md#column-storage)

## Inspecionando os Metadados de Registro da Extensão

`INFORMATION_SCHEMA.EXTENSION_REGISTRATION` expõe a struct de registro do VEF
em memória para cada extensão carregada como um documento JSON. Use-o para
verificar se o servidor analisou corretamente as funções e os tipos da sua
extensão após o `INSTALL EXTENSION`.

```sql theme={null}
SELECT EXTENSION_NAME, NEGOTIATED_PROTOCOL, REGISTRATION_JSON
FROM INFORMATION_SCHEMA.EXTENSION_REGISTRATION
WHERE EXTENSION_NAME = 'my_ext';
```

| Coluna | Tipo | Descrição |
| - | - | - |
| `EXTENSION_NAME` | `VARCHAR(64)` | Nome da extensão instalada. |
| `NEGOTIATED_PROTOCOL` | `BIGINT UNSIGNED` | Versão do protocol VEF negociada entre a extensão e o servidor. |
| `REGISTRATION_JSON` | `VARCHAR(65535)` | Serialização JSON da struct `vef_registration_t`, incluindo os arrays `funcs` e `types` e uma chave `sdk_version` que informa a versão do SDK com a qual a extensão foi compilada. |

## Veja Também

* [Criando Extensões em C++](https://villagesql.com/docs/pt-BR/extensions/build/create.md) — etapas de compilação de ponta a ponta, configuração do CMake e instalação
* [Testes em C++](https://villagesql.com/docs/pt-BR/sdk/cpp/testing.md) — servidor de desenvolvimento local, MTR e depuração de falhas
* [Operações de Tipo](https://villagesql.com/docs/pt-BR/sdk/cpp/type-operations.md) — codificação, decodificação, comparação, hash, tipos parametrizados
* [Referência da API C++](https://villagesql.com/docs/pt-BR/sdk/cpp/extension-api-reference.md) — contratos de VDF, tratamento de nulos e dimensionamento de buffer
* [Arquitetura de Extensões](https://villagesql.com/docs/pt-BR/extensions/build/architecture.md) — ciclo de vida, cache do Victionary, padrões de desempenho e modelo de segurança


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