Skip to main content
As capabilities Preview são recursos fornecidos pelo servidor e expostos às extensões antes que suas APIs sejam finalizadas. Uma extensão que declara uma capability Preview requer vsql_allow_preview_extensions = ON para instalar (consulte Habilitando o Nível Preview) — as extensões que não usam capabilities Preview instalam normalmente, independentemente dessa configuração.
As APIs das capabilities Preview não são estáveis. Uma extensão compilada contra uma capability Preview pode falhar ao carregar após uma atualização do servidor. Quando uma capability se estabiliza, seu cabeçalho é movido para um caminho versionado do SDK C++ estável.

Habilitando o Nível Preview

Defina vsql_allow_preview_extensions = ON com SET PERSIST antes de instalar qualquer extensão que use uma capability Preview:
SET GLOBAL é rejeitado para essa variável — o servidor requer SET PERSIST para que a configuração sobreviva à reinicialização. As extensões com capabilities Preview são carregadas na inicialização, então a variável deve estar configurada como ON quando o servidor iniciar. Se você estiver iniciando o mysqld diretamente (por exemplo, a partir de um script de instalação que inicia o servidor pela primeira vez), passe a flag na linha de comando — o mysqld-auto.cnf ainda não existirá para carregar o valor persistido:
Para desabilitar:
Isso falha se qualquer extensão que usa uma capability Preview estiver instalada no momento. Desinstale essas extensões primeiro e então desative a configuração.

Índice de Capabilities

Padrão de Registro

Para usar uma capability Preview, declare um objeto de capability por valor no escopo do arquivo e passe-o por referência para .with() dentro de make_extension(). O servidor popula o pointer abi do objeto durante o registro:
.with(capability) informa ao servidor quais capabilities a extensão requer. Se vsql_allow_preview_extensions estiver em OFF quando a extensão for instalada, o servidor rejeita a instalação com um erro que nomeia a extensão: ERROR 3219 (HY000): Failed to load VEF extension 'name': extension requires preview capabilities but vsql_allow_preview_extensions is OFF. A mensagem não informa qual capability foi responsável.
Todo objeto de capability declarado em uma extensão deve ser passado para .with() exatamente uma vez. No momento do carregamento, o servidor verifica cada instância de capability declarada em relação ao que .with() recebeu e faz o INSTALL EXTENSION falhar se a regra for violada:
  • Declarada, mas nunca passada para .with(): capability '<Type>' was declared but never passed to .with(); every CapabilityBase-derived static must be registered via .with(cap) in the extension builder
  • Mesma instância passada para .with() mais de uma vez: capability '<Type>' passed to .with() more than once
  • Objeto passado para .with() não é uma capability: .with() received an object that does not inherit vsql::detail::CapabilityBase; not a registered capability
O erro completo aparece como: Failed to load VEF extension '<name>': vef_register returned an error: <message above>.

Acesso ao Keyring

A capability keyring (vsql::preview::keyring) permite que as extensões leiam e escrevam segredos armazenados no componente keyring do MySQL. As extensões a usam para coisas como chaves de API, chaves de criptografia ou outros segredos que não deveriam ficar em tabelas SQL. O nome da capability VEF_PREVIEW_KEYRING_NAME é "vsql::preview::keyring". Um componente keyring deve estar instalado no servidor MySQL para que leituras e escritas tenham sucesso. Sem um, as operações retornam KeyringCapability::Status::UNAVAILABLE.

Valores de Status

KeyringCapability::Status é um enum com escopo retornado por read() (dentro de ReadResult) e por write():

Declarando a Capability

Inclua o cabeçalho, declare um objeto de capability no escopo do arquivo e passe-o para .with():
O objeto g_keyring é populado pelo servidor no momento do carregamento. Os métodos read() e write() retornam Status::UNAVAILABLE em tempo de execução quando nenhum componente keyring está instalado — verifique esse status em cada chamada em vez de depender de uma verificação de disponibilidade separada.

Lendo e Escrevendo

data_id é o identificador da chave. auth_id é o usuário proprietário — passe uma string vazia (ou omita-o em read, que assume {} como padrão) para ler ou escrever chaves internas não associadas a um usuário específico. read retorna um ReadResult por valor. Vincule-o com structured bindings:
Em qualquer status diferente de Status::OK, value fica vazio. write retorna Status diretamente e armazena data sob data_id / auth_id.

Exemplo Completo

Esta é uma versão simplificada da extensão de teste vsql_keyring_reader, na árvore villagesql/test-extensions/ do servidor. Ela registra 2 VDFs: keyring_read e keyring_store.

Serviços do MySQL

A capability mysql_services (vsql::preview::mysql_services) permite que uma extensão consuma serviços do registro do MySQL — os mesmos serviços que um componente do MySQL consome, fornecidos por um componente instalado ou pelo núcleo do servidor. A extensão declara em um único lugar todos os serviços de que precisa; o servidor adquire cada um quando a extensão é carregada e o libera quando a extensão é descarregada. O nome da capability VEF_PREVIEW_MYSQL_SERVICES_NAME é "vsql::preview::mysql_services". Recorra a ela quando um recurso do servidor não tiver uma capability VEF própria. Os atributos de sessão e os próprios serviços de componente do keyring são alcançáveis por esse caminho. Somente o consumo é suportado: registrar a implementação da própria extensão no registro é um trabalho futuro planejado e não faz parte desta capability.

Declarando a Capability

Declare um objeto MysqlServices no escopo do arquivo, nomeie cada serviço que você consome com VSQL_REQUIRE_SERVICE e passe o objeto para .with(). Inclua o cabeçalho do próprio MySQL para cada serviço — é nesse cabeçalho que o tipo e os métodos do serviço são declarados:
VSQL_REQUIRE_SERVICE(services, name, var) declara var, a referência na qual o servidor escreve o serviço adquirido, e registra name em services. Ele declara var como static para você. O objeto MysqlServices também precisa ser static, assim como qualquer referência que você declare manualmente: o servidor escreve através deles no carregamento, e eles precisam sobreviver à extensão.

Fixando uma Implementação Específica

VSQL_REQUIRE_SERVICE usa name duas vezes — como o SERVICE_TYPE(name) do C++ e como a string que o servidor procura no registro. Sob esse nome simples, o servidor adquire a implementação padrão do serviço. Para nomear uma implementação em vez disso, use seu nome qualificado no registro — service.component, a forma que o PROVIDES_SERVICE(component, service) do MySQL gera. Isto pede o leitor de keyring do componente component_keyring_file em vez do padrão:
Um nome qualificado é adquirido da mesma maneira que um nome simples, então vale a regra de sempre: se aquela implementação exata não estiver registrada, a extensão falha ao instalar em vez de recorrer a outra.

Compilando com os Cabeçalhos do MySQL

As definições de serviço pertencem ao framework de componentes do MySQL, e não ao VEF, e o servidor não as instala. Portanto, mysql/components/services/*.h está ausente do SDK de extensões e de tudo o que é gerado por make install, o que inclui o tarball de release e a imagem Docker. Uma extensão que consome serviços é compilada contra uma árvore de código-fonte do servidor VillageSQL: As extensões de teste internas à árvore obtêm ambos a partir da flag MYSQL_HEADERS de vsql_add_test_extension(), que os repassa como MYSQL_INCLUDE_DIR e MYSQL_GENERATED_INCLUDE_DIR. Uma compilação fora da árvore define seus próprios caminhos de include. Duas falhas de compilação aparecem em um lugar diferente da linha que as causou. Omitir o cabeçalho MySQL de um serviço deixa VSQL_REQUIRE_SERVICE com um nome que não resolve para nada, então o erro aparece na macro em vez de no include ausente (clang 17):
Algumas definições de serviço usam size_t sem incluir <cstddef>, então colocar um desses cabeçalhos antes de todos os cabeçalhos villagesql falha dentro do próprio cabeçalho do MySQL:
Inclua <cstddef> primeiro, como fazem os exemplos desta página.

Chamando um Serviço

Uma referência de serviço expõe o seu próprio valid(), e -> encaminha para o serviço. Use . para a referência e -> para o serviço:
Verifique valid() antes de cada chamada com ->. -> retorna o pointer adquirido, que é nulo quando o serviço não foi adquirido. Um serviço que falha ao ser adquirido faz a instalação falhar, então, dentro de uma função em execução, um serviço requerido é válido. A verificação ainda importa, porque um ServiceRef declarado manualmente e nunca passado para require() nunca é escrito: ele compila, a extensão instala, e valid() é falso por toda a vida da extensão. O que um serviço é — seus métodos, seus parâmetros e o que eles retornam — é documentado pelo MySQL, não aqui. Para um serviço chamado NAME, leia include/mysql/components/services/NAME.h na árvore do servidor: seu bloco BEGIN_SERVICE_DEFINITION(NAME) declara cada método com a sua própria documentação. Chame-os exatamente como aquele cabeçalho especifica, incluindo a convenção do MySQL de que um retorno bool false significa sucesso e true significa falha.

Falha na Aquisição

Todo serviço declarado é adquirido quando a extensão é carregada, antes que qualquer uma de suas funções possa ser chamada, então um serviço que não está registrado faz o carregamento falhar em vez de aparecer mais tarde. O INSTALL EXTENSION falha e nomeia o serviço. vsql_mysql_services_missing_test, abaixo, é uma extensão de teste interna à árvore que requer um serviço que o registro não tem. Não é algo que você possa instalar — é como a falha foi capturada, e é o que a sua própria extensão produz se ela requerer um serviço que este servidor não fornece:
Outras duas falhas de instalação chegam a esta capability de fora dela: deixar o objeto MysqlServices fora de .with() e instalar em um servidor com vsql_allow_preview_extensions OFF. Ambas são cobertas em Padrão de Registro.

Exemplo Completo

Uma versão simplificada de vsql_mysql_services_session_test, na árvore villagesql/test-extensions/ do servidor. Ela lê o comando SQL em execução na sessão que a chamou compondo dois serviços: um devolve o THD atual, o outro lê um atributo nomeado a partir dele. Ambos são serviços do núcleo do servidor, registrados em todo servidor, então nada precisa ser instalado antes:
Instale-a e chame a função:

Variáveis de Status

A capability status_var (vsql::status_var) permite que uma extensão exponha contadores long long e double como variáveis de status do MySQL. A extensão é dona do armazenamento e escreve nele; o servidor lê através dos pointers cada vez que a variável de status é consultada. Construa a capability com vsql::preview_status_var::make_capability(), passando uma lista entre chaves de descritores de make_int(name, value_ptr) ou make_double(name, value_ptr). O template deduz a contagem a partir da lista entre chaves, então nenhum tamanho explícito é necessário.

Exemplo Completo

make_int requer um long long *; make_double requer um double *. Esses são os únicos dois tipos compatíveis.

Acessando a partir do SQL

Após INSTALL EXTENSION my_ext, a variável fica visível com o nome da extensão como prefixo:
Incrementos concorrentes de múltiplas threads de consulta usando um ++ não-atômico podem ocasionalmente ser perdidos; isso é aceitável para contadores aproximados de chamadas expostos via SHOW STATUS.

Variáveis de Sistema

A capability sys_var (vsql::sys_var) permite que uma extensão registre variáveis de sistema do MySQL respaldadas por armazenamento de propriedade da extensão. Há suporte para quatro tipos: BOOL (bool *), INT (long long *), DOUBLE (double *) e STR (char **). Os descritores INT e DOUBLE também carregam limites min_val e max_val; todos os descritores carregam um valor padrão e um comentário. Construa a capability com vsql::preview_sys_var::make_capability() e as funções de fábrica correspondentes make_bool, make_int, make_double e make_str. O objeto de capability também expõe get() e set() para acesso programático a partir do código da extensão. Ambos retornam false em caso de sucesso. Para reagir a mudanças de valor, encadeie .on_change<&fn>() em um descritor. O callback recebe um sv::SysVarChange com var_name() e acessadores tipados (as_int(), as_real(), as_str()). O servidor chama esse callback enquanto mantém o seu lock global de variáveis de sistema. Ler ou escrever outra variável desta extensão através do seu pointer de armazenamento é seguro ali, e as outras sessões veem o novo valor imediatamente, porque o servidor lê essas variáveis sob o mesmo lock.
Chamar o get() ou o set() da capability, executar SQL ou esperar por uma thread que faça uma dessas coisas causa deadlock nesse lock. Mantenha o callback curto e não bloqueante, entregue a uma thread de trabalho o trabalho que precisa de SQL, ou libere o LOCK_global_system_variables em volta da parte bloqueante e retome-o antes de retornar, como o event_scheduler_update() faz em sql/sys_vars.cc.
O objeto de capability deve ter duração de armazenamento estática. O MySQL escreve diretamente nos pointers de armazenamento quando o usuário define uma variável.

Exemplo Completo

Acessando a partir do SQL

Após INSTALL EXTENSION my_ext, as variáveis ficam acessíveis usando o nome da extensão como prefixo de componente:

Lendo e Escrevendo a partir do Código da Extensão

Para variáveis INT e BOOL, leia o pointer de armazenamento global diretamente — o MySQL atualiza esses valores atomicamente. Para atualizar uma variável através do MySQL (de forma que travamento, validação de intervalo e persistência sejam tratados pelo servidor), chame SYS_VARS.set(extension_name, var_name, scope, value). Tanto set quanto get retornam false em caso de sucesso. Nenhum dos dois pode ser chamado a partir de um callback on_change: ambos causam deadlock no lock de variáveis de sistema.
O argumento scope controla a persistência:

Thread de Trabalho

A capability thread worker (vsql::preview::thread_worker) permite que uma extensão execute uma thread em segundo plano conduzida pelo servidor. A thread é iniciada e parada através de uma variável de sistema de controle que o servidor registra no carregamento da extensão; o servidor invoca a função de trabalho da extensão em um temporizador periódico, na prontidão de um descritor de arquivo, ou em resposta a eventos de habilitação/desabilitação. O nome da capability VEF_PREVIEW_THREAD_WORKER_NAME é "vsql::preview::thread_worker".

Declarando a Capability

Inclua o cabeçalho, declare um ThreadWorkerCapability instanciado sobre a sua função de trabalho no escopo do arquivo e passe-o para .with():
A função de trabalho é fornecida como um argumento de template não-tipo (ThreadWorkerCapability<&my_work>), portanto deve ser uma função com a assinatura mostrada abaixo. O primeiro argumento do construtor é o sufixo do nome da thread; o segundo argumento, opcional, sobrescreve o nome da variável de sistema de controle.

Assinatura da Função de Trabalho

reason indica por que o servidor chamou a função. thread é o handle de propriedade do servidor para este worker (NULL na chamada inicial de VEF_WAKEUP_ENABLE — veja abaixo). arg é o pointer opaco registrado no descritor; ele é repassado inalterado.

Ciclo de Vida do Wakeup

O servidor chama a função de trabalho com um de quatro motivos: O parâmetro thread é NULL quando o motivo é VEF_WAKEUP_ENABLE, porque o handle da thread ainda não existe nesse ponto. Para os outros três motivos, thread é não-nulo.

Valor de Retorno do Wakeup

A função de trabalho retorna um vef_next_wakeup_t para atualizar a configuração do próximo wakeup. Um valor zero em qualquer um dos campos significa “manter a configuração atual” — retorne uma struct inicializada por valor (return {};) para deixar ambos inalterados. Para definir um novo descritor de arquivo de poll, retorne seu valor (deve ser maior que zero). Para limpar um descritor de arquivo de poll existente, retorne -1 em poll_fd. O valor de retorno é ignorado quando o motivo é VEF_WAKEUP_DISABLE.

Nome da Thread e Variável de Controle

Dois campos no descritor controlam a nomenclatura:
  • suffix — o sufixo do nome da thread. O servidor antepõe o nome da extensão, produzindo nomes de thread como my_ext/monitor.
  • var_name — opcional. Quando não-nulo, o servidor registra exatamente este nome como a variável de sistema de controle. Quando nulo, o servidor usa o padrão {suffix}_enabled.
A variável de controle é uma variável de sistema registrada pelo servidor, então ela recebe o nome da extensão como prefixo de componente. Para a extensão my_ext com o sufixo monitor, a variável é my_ext.monitor_enabled. Defini-la como ON inicia o worker: o servidor chama a função de trabalho com VEF_WAKEUP_ENABLE e então cria a thread, de modo que a instrução só retorna depois que essa primeira chamada termina. Defini-la como ON de novo enquanto o worker já está em execução não faz nada. Defini-la como OFF retorna depois que a thread termina. O servidor libera o seu lock global de variáveis de sistema em volta das duas operações, então a função de trabalho pode ler variáveis de sistema e executar SQL.

Exemplo Completo

Uma extensão mínima com um único worker periódico que incrementa um contador de heartbeat a cada tique do temporizador.
Com essa extensão instalada (e vsql_allow_preview_extensions = ON), o servidor registra uma variável de sistema heartbeat_enabled sob o nome da extensão. Para uma extensão chamada my_ext, habilite o worker com:

Consulta SQL

A capability sql_query (vsql::preview::sql_query) permite que uma extensão execute instruções SQL a partir de uma thread em segundo plano. As consultas são executadas dentro do servidor através da vtable da capability — as extensões não são vinculadas a nenhuma biblioteca cliente do MySQL. O nome da capability VEF_PREVIEW_SQL_QUERY_NAME é "vsql::preview::sql_query".
Uma sessão SQL deve ser aberta a partir de um callback de thread worker usando o vef_thread_handle_t * daquele callback. open() não é válido a partir de VDFs ou de threads arbitrárias criadas pela extensão — ele requer o contexto de sessão do worker.

Declarando a Capability

Inclua o cabeçalho, declare um SqlQueryCapability no escopo do arquivo e passe-o para .with(). Ele é tipicamente registrado junto de um ThreadWorkerCapability, já que as sessões são abertas a partir do callback do worker:
g_sql.open(handle) retorna uma Session. Verifique-a com operator bool antes de usá-la; uma Session inválida indica que a vtable da capability não foi vinculada ou que o servidor não conseguiu alocar uma sessão. A Session é move-only e se fecha na destruição.

Executando Consultas

Uma Session produz uma SqlQuery via session.sql(sv). A consulta pode ser executada em dois modos:
  • execute() — executa a instrução e armazena em buffer todo o conjunto de resultados em um Result. Itere as linhas chamando next() no ritmo do chamador.
  • for_each(fn) — executa a instrução e invoca fn uma vez por linha à medida que as linhas são produzidas, sem armazenar em buffer. O Result retornado carrega apenas diagnósticos (nenhuma linha).
Ambos retornam um Result. Um Result não-nulo não significa que a instrução teve sucesso — chame has_error() para descobrir. Em buffer (execute):
column_str() retorna um string_view que é válido apenas até a próxima chamada de next() ou até que o Result seja destruído. Copie-o se um tempo de vida mais longo for necessário. Um string_view com data() == nullptr indica SQL NULL. Em streaming (for_each):
A Row passada para o callback é válida apenas durante a chamada — não armazene referências a ela entre as linhas. O Result retornado por for_each não contém linhas em buffer; next() nele não produzirá dados. Use-o apenas para has_error(), error(), warning_count() e warning(i).

Diagnósticos

Tanto execute() quanto for_each() expõem diagnósticos através do Result retornado. Um diagnóstico é um Diag:
Result expõe:
error() retorna um Diag construído por padrão (errno_ == 0) quando a instrução teve sucesso. warning(i) retorna um Diag construído por padrão quando i >= warning_count(). Os views sqlstate e message apontam para armazenamento de propriedade do Result e se tornam inválidos quando o Result é destruído — copie-os se eles precisarem sobreviver a ele.

Exemplo Completo

Um worker que executa uma consulta em buffer e uma consulta em streaming a cada tique, registrando diagnósticos de ambas:

Armazenamento de Colunas

O column storage permite que uma extensão registre um layout binário personalizado em disco diretamente com o InnoDB para um de seus tipos personalizados, em vez de rotear os bytes do tipo através do payload VARBINARY da linha. Use-o quando o seu tipo precisa de um formato em disco que o VARBINARY não consegue expressar — por exemplo, um array compactado de floats que precisa residir em páginas dedicadas. Este é um recurso de capability: ele habilita novos layouts de armazenamento, não é um botão de ajuste para os existentes.
O column storage é uma ABI Preview — em desenvolvimento ativo e pode mudar entre versões. Atualmente cobre apenas a persistência em nível de linha; a indexação sobre colunas com armazenamento personalizado ainda não está disponível.

Declarando as Capabilities

Duas capabilities Preview trabalham juntas:
  • vsql::preview::storage — abre acesso à infraestrutura de armazenamento do InnoDB (mini-transações, segmentos, páginas). Declare uma StorageCapability no escopo do arquivo.
  • vsql::preview::column_store — vincula uma implementação de armazenamento por tipo a um dos tipos personalizados da extensão. Declare uma ColumnStoreCapability no escopo do arquivo usando make_column_store<Ctx>(TYPE).…build().
Ambas devem ser passadas para .with() em make_extension():
make_column_store<MyCtx>(MY_TYPE) liga a implementação a um tipo personalizado registrado na mesma extensão. Todos os sete slots são obrigatórios no momento do build(), porque cada um mapeia para um ponto distinto no ciclo de vida da coluna que o InnoDB alcançará durante a operação normal.

As Sete Funções de Armazenamento

Toda função recebe storage::Column::StorageCtx<MyCtx>*, cujo acessador user() retorna o estado por coluna da extensão e cujo arena() fornece alocação gerenciada pelo servidor para objetos auxiliares. Toda função retorna false em caso de sucesso e true em caso de erro, escrevendo uma mensagem em error_msg (capacidade error_msg_len) para que as falhas apareçam ao cliente SQL.
mark_delete e purge são distintos porque o MVCC do InnoDB requer que as linhas excluídas permaneçam legíveis por snapshots mais antigos até que o purge seja executado.

Contexto por Coluna e a Arena

O SDK C++ constrói MyCtx por padrão antes de chamar create ou loadctx->user() já está populado quando a sua função é acionada. MyCtx deve ser construível por padrão; o SDK C++ chama T() sem argumentos. Use ctx->user() diretamente para inicializar o estado. Não chame ctx->arena().construct<MyCtx>() — isso aloca uma segunda instância não utilizada e ctx->user() não aponta para ela.
load segue o mesmo padrão — ctx->user() é pré-populado e storage_ref carrega o valor compactado armazenado por ctx->set_ref() em create:
Use ctx->arena() apenas para alocar objetos auxiliares que são grandes demais ou dinâmicos demais para embutir diretamente em MyCtx. O SDK C++ destrói a arena (e chama ~MyCtx()) automaticamente após o retorno de drop, independentemente de drop ter sucesso.

Utilitários de Acesso ao InnoDB

Inclua <villagesql/preview/storage_api.h> para os primitivos do InnoDB. Todas as leituras e escritas de página devem ocorrer dentro de uma mini-transação:
Confirmar a mini-transação libera os latches de página e escreve os registros do redo log que tornam as mudanças duráveis. Segmentos são reservados no momento do create — veja os exemplos de create e load em Contexto por Coluna acima para o padrão completo de configuração. Durante as operações DML, obtenha uma referência de segmento a partir da página raiz para alocar novas páginas:
Páginas são lidas com um latch compartilhado e escritas com um exclusivo. Passe mtr_ref para as chamadas de escrita para que o InnoDB registre a mudança:
Constantes de layout de página: Ler ou escrever dentro das regiões de cabeçalho ou trailer corrompe a página — o InnoDB usa esses intervalos de bytes para sua própria contabilidade e checksum.

Eventos de Instrução

A capability statement event (vsql::preview::statement_event) executa um handler fornecido pela extensão após cada consulta terminar de executar. O servidor invoca o handler sincronamente na própria thread da consulta e passa metadados de execução — o texto da consulta, tempo, contagens de linhas, identidade da conexão e indicadores de qualidade do otimizador. Use-o para registro de consultas lentas, auditoria ou coleta de métricas. O nome da capability VEF_PREVIEW_STATEMENT_EVENT_NAME é "vsql::preview::statement_event".

Declarando a Capability

Declare um StatementEventCapability, instanciado sobre a fase de disparo e a sua função handler, no escopo do arquivo e passe-o para .with():
O primeiro argumento de template é a fase de disparo, um valor vef_statement_event_phase_t. VEF_STATEMENT_EVENT_POSTEXECUTE dispara após uma consulta terminar de executar, em caso de sucesso ou falha, e é a única fase implementada nesta versão. Os outros valores de vef_statement_event_phase_t são reservados; declarar um handler para um deles faz o servidor rejeitar o INSTALL EXTENSION.

Argumentos do Handler

StatementEventArgs é um view somente leitura da consulta concluída; na fase POSTEXECUTE todos os campos estão populados. Acessadores selecionados: Como query() retorna a forma reescrita do servidor quando existe uma, as instruções que carregam credenciais chegam com o segredo ofuscado em vez de em texto claro, correspondendo à forma como os logs geral, lento e binário já os ofuscam: SET PASSWORD, CREATE/ALTER USER ... IDENTIFIED BY, CHANGE REPLICATION SOURCE ... SOURCE_PASSWORD e CREATE SERVER ... OPTIONS(PASSWORD ...). As instruções sem regra de reescrita são entregues literalmente. Os acessadores de string como query(), sqlstate() e error_message() apontam para armazenamento que é válido apenas durante a chamada do handler — copie os bytes se você precisar deles depois que o handler retornar. StatementEventResult::error_msg(fmt, ...) escreve uma mensagem formatada com printf. Na fase POSTEXECUTE a mensagem é informativa: o servidor a registra, mas não a propaga para o cliente.

Exemplo Completo

Uma forma condensada da extensão de teste vsql_slow_query_log. Ela registra cada consulta cujo tempo de execução excede um limiar, combinando a capability statement event com variáveis de sistema para configuração em tempo de execução:

Habilitando a partir do SQL

Com o nível Preview habilitado (consulte Habilitando o Nível Preview), instale a extensão e configure-a através de suas variáveis de sistema:
Cada consulta mais lenta que o limiar é anexada ao arquivo de log configurado:

Métodos de Autenticação

A capability auth (vsql::preview::auth) permite que uma extensão forneça um método de autenticação do servidor. Uma conta opta por ele com CREATE USER ... IDENTIFIED WITH <nome-do-metodo>; no momento da conexão, quando esse nome não é um plugin de autenticação do MySQL carregado, o servidor consulta o registro de autenticação do VEF e invoca o handler da extensão sobre o handshake. Use-o para autenticar contas contra uma fonte de credenciais que o servidor não conhece — um token de portador, um provedor de identidade externo ou um desafio personalizado — sem escrever um plugin de autenticação do MySQL. O nome da capability VEF_PREVIEW_AUTH_NAME é "vsql::preview::auth". O handler é uma função tipada que recebe um AuthContext: ele conversa com o cliente lendo e escrevendo pacotes do handshake através desse contexto de propriedade do servidor, e nunca vê as estruturas internas de autenticação do MySQL.
O resultado da autenticação é fail-closed. O servidor trata qualquer coisa diferente de AuthResult::kOk como uma conexão negada — deliberadamente não há resultado “talvez” nem fail-open. Um handler que retorna AuthResult::kReject, retorna AuthResult::kError ou nunca define a conta efetiva nega o login.

Declarando a Capability

Inclua o cabeçalho, escreva um handler tipado, construa um descritor com o construtor fluente make_auth<> e entregue o descritor a um token AuthCapability que você passa para .with(). Os cabeçalhos das capabilities Preview não fazem parte do guarda-chuva <villagesql/vsql.h>, então inclua <villagesql/preview/auth.h> explicitamente:
O construtor tem seis peças: AuthCapability g_auth{descriptor} é o token autorregistrável consumido por .with(). Declare-o static para que ele sobreviva ao registro. client_plugin é opcional. make_auth define o plugin anunciado por padrão como "mysql_clear_password" — o menor denominador comum que todo cliente MySQL traz — então um método que nunca chama .client_plugin() ainda instala e um cliente ingênuo ainda se conecta. Chame .client_plugin(name) para pedir um plugin diferente; o mysql_clear_password recebe um token de portador literalmente no campo da senha. Um cliente que oferece um plugin diferente daquele que o método pede é trocado para o plugin pedido e reenvia sua credencial literalmente, o que custa um round trip e exige um cliente disposto a fazer essa troca. .accepts_client_plugin(&callback) permite que o método mantenha o plugin oferecido: o servidor passa cada nome oferecido ao callback, incluindo o plugin pedido, que é aceito independentemente do que o callback retornar. Um método que não define nenhum callback não aceita nenhuma outra oferta, então toda outra oferta é trocada pelo plugin pedido. Aceitar é definitivo — o servidor não volta depois para o plugin pedido — então aceite apenas um plugin cujo formato o handler realmente entenda. O servidor consulta o callback durante a negociação do handshake, antes da primeira leitura do handler, então ele deve ser um predicado puro: sem E/S de pacotes, sem bloqueio, sem efeitos colaterais.

O Contrato do Handler

O handler corresponde ao tipo AuthHandler — ele recebe um AuthContext & e retorna um AuthResult:
Ele é invocado de forma síncrona na thread de conexão durante o handshake. O AuthContext envolve o contexto por tentativa de propriedade do servidor; mantenha-o apenas durante a chamada e não o retenha. Chame seus métodos em vez de encadear um pointer de contexto por uma tabela de funções. Os métodos que um handler baseado em token usa: O handler retorna um de três resultados: Tanto AuthResult::kReject quanto AuthResult::kError negam a conexão. Somente AuthResult::kOk tem sucesso. Quando o handler mapeia a conta que está se conectando para uma conta efetiva diferente — como o exemplo abaixo mapeia a conta que se conecta para vsql_auth_test_user — isso é proxying, e requer um GRANT PROXY, exatamente como no caminho de autenticação por plugin do MySQL.

Preparando Papéis Ativos

c.set_active_roles(roles, n_roles) prepara os papéis que devem estar ativos na sessão, substituindo a ativação de papéis padrão da conta neste login. roles é um array de n_roles nomes terminados em NUL; as strings são copiadas, então quem chama não precisa mantê-las. O servidor as aplica depois da resolução da conta, usando a mesma ativação com verificação de concessão do SET ROLE: apenas papéis realmente concedidos à conta autenticada são ativados, e nomes que não foram concedidos são ignorados silenciosamente — de modo que um token nunca pode conceder ou escalar privilégios além do que o DBA provisionou. Passar n_roles == 0 não ativa nenhum papel (equivalente a SET ROLE NONE).

Exemplo Completo

Um autenticador mínimo, condensado a partir da extensão vsql_auth_test na árvore de código-fonte do servidor em villagesql/test-extensions/vsql-auth-test/, que nenhum release inclui. Ele aceita um token fixo, mapeia a conexão para vsql_auth_test_user e pede mysql_clear_password para que o token chegue literalmente no campo da senha. (A extensão interna à árvore adiciona caminhos de token extras, um callback .accepts_client_plugin() e as duas adesões descritas abaixo para conduzir a sua suíte de testes.)

Vinculando uma Conta e Conectando

Com o nível Preview habilitado (consulte Habilitando o Nível Preview), instale a extensão e vincule uma conta ao método. Como o handler mapeia para uma segunda conta, crie essa conta também e conceda a ela o privilégio PROXY que permite à conta que se conecta assumir a sua identidade:
CREATE USER ... IDENTIFIED WITH vsql_auth_test é aceito porque vsql_auth_test é um método de autenticação VEF registrado — da mesma forma que o nome de um plugin instalado é aceito. Somente a forma IDENTIFIED WITH <method> é aceita, opcionalmente com AS '...'. Adicionar BY '...' pede ao método que transforme uma senha em uma credencial armazenada — o trabalho que um plugin do MySQL faz através de generate_authentication_string() — e nenhum método de autenticação VEF declara esse hook hoje, então o servidor o rejeita:
O nome do método vinculado é gravado na coluna plugin da conta em vez do padrão da tabela, e é isso que o próximo login da conta lê:
O método pede mysql_clear_password, então o cliente precisa passar --enable-cleartext-plugin para enviar o token em texto claro. Com um token correto, a sessão é executada como a conta mapeada e expõe a conta que se conecta através de @@external_user:
Desinstalar a extensão remove o método; as contas vinculadas a ele não conseguem mais se autenticar:

Criando Contas Automaticamente

Um método também pode tratar logins de contas que ainda não existem e fazer o servidor criar a conta como parte do login bem-sucedido. Sem isso, uma conta desconhecida é rejeitada antes que qualquer método seja executado. Adira com .auto_create(&callback). O callback não recebe argumentos e retorna bool; o servidor o chama a cada login de conta desconhecida em vez de lê-lo uma única vez no registro, então o método pode seguir uma configuração de tempo de execução própria em vez de congelar a escolha quando a extensão carrega:
Deixar .auto_create() de fora, ou retornar false do callback, mantém o comportamento padrão: uma conta desconhecida é negada. Apenas um método instalado pode aderir por vez — se dois retornarem true, o servidor se recusa a adivinhar, registra um aviso no log de erros e rejeita contas desconhecidas como se nenhum tivesse aderido. No handler, c.account_unknown() distingue os dois casos. Valide a credencial primeiro, depois descreva o que criar e autentique-se como ela:
request_provision(account, roles, n_roles) registra a intenção e não retorna nada. O servidor executa o DDL ele mesmo, depois que o handler retorna AuthResult::kOk, e apenas para um login que foi roteado como uma conta desconhecida — então um login que o handler acaba negando não cria nada, e um pedido que nomeia uma conta que já existe é ignorado. O que o servidor executa é CREATE USER IF NOT EXISTS <account>@'%' IDENTIFIED WITH <method>, seguido de um GRANT por papel nomeado: a conta é sempre criada para o host % e vinculada ao método que a autenticou, e account não precisa ser o nome do usuário que se conecta. Se a criação não puder ser feita — em um servidor super_read_only, por exemplo — o login falha em vez de prosseguir sem uma conta. Os papéis se comportam como em Preparando Papéis Ativos: o DBA é dono deles. Cada nome já deve existir como um papel concedível, e um que não puder ser concedido é registrado no log e ignorado em vez de fazer o login falhar, de modo que um token pode nomear um papel mas nunca criar ou escalar um. O nome da conta vem do cliente, então o servidor o delimita com aspas como um identificador — um nome forjado vira uma conta com nome estranho, nunca uma segunda instrução. A extensão vsql_auth_test provisiona o usuário que se conecta com o papel vsql_role_granted e coloca a adesão atrás de vsql_auth_test.auto_create, que começa em OFF. Ative-a e crie o papel primeiro, depois conecte-se como uma conta que não existe:
A conta agora existe, vinculada ao método, com o papel concedido:
Um token errado ainda falha de forma fechada, e não provisiona nada:
Aderir torna a diferença entre uma conta desconhecida e uma existente observável para qualquer pessoa que tenha uma credencial válida, algo que a rejeição padrão de contas desconhecidas esconde deliberadamente. Essa é a troca que este recurso faz; pondere-a antes de habilitar a adesão em um método cujas credenciais sejam amplamente distribuídas.

Concedendo Papéis Automaticamente

Por padrão, um papel que um token nomeia só tem efeito se a conta já o possui, e um que ela não possui é registrado no log e ignorado. .auto_grant(&callback) muda isso: o servidor concede à conta os papéis preparados, de modo que o token decide quais papéis a sessão recebe, em vez de apenas quais dos papéis existentes da conta ativar. O callback tem o mesmo formato que o de .auto_create() — sem argumentos, retorna bool — e o servidor o chama a cada login, então ele pode seguir uma configuração de tempo de execução:
As duas adesões são independentes. .auto_create() governa logins de contas que não existem; .auto_grant() governa a concessão à conta para a qual um login se resolve, tenha essa conta acabado de ser criada ou não. Deixar .auto_grant() de fora, ou retornar false, mantém o padrão de apenas ativar. A concessão persiste — é um GRANT comum, não uma ativação apenas de sessão — e é aditiva: o servidor nunca revoga um papel que o token deixou de nomear. vsql_auth_test expõe isso como vsql_auth_test.auto_grant, também começando em OFF. O seu token -token-roles prepara vsql_role_granted e vsql_role_denied, e a conta abaixo não possui nenhum dos dois. Com a configuração desligada, o login deixa os papéis da conta em paz:
Conectando-se como auth_user com aquele token, da mesma forma que acima, e perguntando o que está ativo:
Ligue a configuração e repita o mesmo login:
Os dois papéis agora estão ativos, e o SHOW GRANTS mostra a concessão que o servidor adicionou:
Com .auto_grant() ligado, um token válido basta para obter qualquer papel que ele nomeie. O papel já precisa existir, então um token ainda não pode inventar privilégios, mas o DBA deixa de decidir quais papéis existentes uma conta pode alcançar — quem decide é o método.