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.
Habilitando o Nível Preview
Definavsql_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:
Í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.
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():
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:
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 testevsql_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 objetoMysqlServices 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:
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):
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:
<cstddef> primeiro, como fazem os exemplos desta página.
Chamando um Serviço
Uma referência de serviço expõe o seu própriovalid(), e -> encaminha para o
serviço. Use . para a referência e -> para o serviço:
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. OINSTALL 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:
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 devsql_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:
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ósINSTALL EXTENSION my_ext, a variável fica visível com o nome da extensão
como prefixo:
++
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.
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ósINSTALL 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), chameSYS_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.
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 umThreadWorkerCapability instanciado sobre a sua
função de trabalho no escopo do arquivo e passe-o para .with():
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
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 comomy_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.
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.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".
Declarando a Capability
Inclua o cabeçalho, declare umSqlQueryCapability 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
UmaSession 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 umResult. Itere as linhas chamandonext()no ritmo do chamador.for_each(fn)— executa a instrução e invocafnuma vez por linha à medida que as linhas são produzidas, sem armazenar em buffer. OResultretornado carrega apenas diagnósticos (nenhuma linha).
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):
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
Tantoexecute() 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.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 umaStorageCapabilityno escopo do arquivo.vsql::preview::column_store— vincula uma implementação de armazenamento por tipo a um dos tipos personalizados da extensão. Declare umaColumnStoreCapabilityno escopo do arquivo usandomake_column_store<Ctx>(TYPE).…build().
.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 recebestorage::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óiMyCtx por padrão antes de chamar create ou load —
ctx->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:
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:
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:
mtr_ref para as chamadas de escrita para que o InnoDB registre a mudança:
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 umStatementEventCapability, instanciado sobre a fase de disparo e a sua
função handler, no escopo do arquivo e passe-o para .with():
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 testevsql_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: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.
Declarando a Capability
Inclua o cabeçalho, escreva um handler tipado, construa um descritor com o construtor fluentemake_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:
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 tipoAuthHandler — ele recebe um AuthContext & e
retorna um AuthResult:
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ãovsql_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égioPROXY 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:
plugin da conta em vez do padrão
da tabela, e é isso que o próximo login da conta lê:
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:
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:
.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:
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:
.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:
auth_user com aquele token, da mesma forma que acima, e
perguntando o que está ativo:
SHOW GRANTS mostra a concessão que o
servidor adicionou:

