villagesql. Para o tutorial de introdução, consulte Criando Extensões em Rust. Para tipos personalizados, consulte Tipos Personalizados em Rust.
InValue
InValue é o enum que o servidor passa para cada argumento de função. Sua função recebe args: &[InValue] e deve verificar cada argumento antes de usar seu valor.
Sempre faça a correspondência de
Null explicitamente. Chamar .unwrap() ou fazer a correspondência de padrão apenas das variantes de valor é um bug — o NULL do SQL é uma entrada normal, não um erro.
VdfReturn
VdfReturn é o que sua função retorna ao servidor. Construa-o com uma das funções associadas:
Aviso vs. erro:
Use
warning para falhas de validação de entrada do usuário em que faz sentido continuar com o restante do conjunto de resultados. Um aviso não faz a instrução falhar, inclusive INSERT e UPDATE no modo estrito. Use error para condições em que prosseguir é inseguro: dados armazenados corrompidos, violações de invariantes internas. Um erro fatal aborta a instrução inteira.
VdfReturn para cada falha, e não cause um panic. O SDK não
captura um panic em uma função, agregação, prerun ou postrun, nem nas operações
de um tipo personalizado, então um panic que chega ao ponto de entrada
extern "C" do SDK aborta o processo do servidor. Isso inclui .unwrap() e .expect() em um None ou
em um Err, e a indexação de slice fora dos limites.
macro extension!
extension! gera os pontos de entrada VEF que o servidor chama ao carregar seu arquivo VEB. Ele deve aparecer exatamente uma vez no crate.
types: e requires: são opcionais por si só, mas funcs: deve estar sempre presente — escreva funcs: [] para uma extensão apenas de tipos. Uma extensão apenas de funções omite types:. Um bloco extension! com funcs: [] e sem tipos é válido, mas produz uma extensão que não faz nada.
requires: declara as capabilities Preview que a extensão usa, como referências a objetos static de capability. Ela deve vir por último, depois de uma seção funcs: — inclua funcs: [] se a extensão não registrar nenhuma função.
macro func!
func! declara uma função chamável via SQL. Oito formas — quatro sem estado por instrução (sem parâmetros, apenas buffer_size, apenas deterministic, ambos), duas que anexam estado por instrução por meio de uma função prerun e mais duas que adicionam uma postrun: opcional sobre essas. Consulte Estado por instrução abaixo.
Constantes de tipo para uso em
func!:
Estado por instrução
Algumas funções precisam de um estado que abrange todas as linhas de uma única instrução — um contador de chamadas, um acumulador. Declare o tipo do estado comstate: e uma função de configuração com prerun:. A função prerun é executada uma vez, antes da primeira linha; a função de linha então é executada uma vez por linha com acesso &mut a esse estado.
Uma função prerun tem a assinatura fn(PrerunArgs, PrerunResult<T>), e a função de linha que ela alimenta recebe o estado primeiro: fn(state: &mut T, args: &[InValue]) -> VdfReturn. T é o tipo nomeado por state:, e o compilador verifica se a prerun e a função de linha concordam sobre ele.
PrerunArgs::len() é o número de argumentos que cada linha vai receber, e PrerunArgs::is_empty() é verdadeiro quando a função foi chamada sem argumentos.
Você não deve liberar o estado por conta própria: func! sempre gera a postrun que o descarta quando a instrução termina, então liberá-lo não é responsabilidade sua, mesmo quando você fornece a sua própria finalização. Essa etapa de descarte é o oposto do SDK C++, onde sua postrun tem que chamar delete_state<T>() — consulte Estado por Instrução.
Nomeie uma postrun: postrun_fn, fn(&mut StateType), para executar primeiro a sua própria finalização: ela é executada uma vez após a última linha, enquanto o estado ainda pode ser tomado emprestado, e antes do descarte feito pelo próprio SDK. Consulte examples/vsql_postrun_order/src/lib.rs no repositório do SDK Rust, cuja saída de teste registrada inclui a linha prerun,postrun,drop|rows_seen=3, confirmando a ordem.
Uma extensão completa cuja função retorna o próprio índice de chamada dentro da instrução:
call_index() é executada três vezes e retorna 1, depois 2, depois 3 — um valor por linha. SUM soma esses três valores, o que dá 6.
O segundo SELECT retorna o mesmo total do primeiro, não um maior: o contador é alocado para uma instrução e descartado quando ela termina.
macro agg_func!
agg_func! declara uma função SQL de agregação — no estilo de SUM/COUNT, chamada sobre as linhas de cada grupo em vez de uma vez por linha. Duas formas:
O acumulador é alocado uma vez por instrução e descartado quando a instrução termina —
agg_func! gera tanto a prerun que o cria quanto a postrun que o descarta, então você nunca escreve nenhuma das duas. clear_fn é o que dá a você o comportamento por grupo: com GROUP BY, o mesmo acumulador é reutilizado entre os grupos, então qualquer campo que não deva vazar entre grupos tem que ser redefinido ali.
Uma agregação completa equivalente a SUM — o exemplo vsql_agg_sum no repositório do SDK:
accumulate fazer a correspondência apenas de InValue::Int é o que pula os NULLs, correspondendo ao SUM embutido. O flag seen é o que faz um grupo todo de NULLs e um grupo vazio retornarem NULL em vez de 0:
macro varargs_func!
varargs_func! declara uma VDF que aceita qualquer número de argumentos, de qualquer tipo. A lista de parâmetros é escrita [..] — um literal obrigatório, não o [] usado para uma func! de aridade zero.
Oito formas — três formatos, cada um com uma forma abreviada e uma forma completa que adiciona
buffer_size e deterministic juntos (nunca isoladamente). O formato state:/
prerun: tem mais duas sobre essas, adicionando uma postrun: opcional —
mesmo significado que em func!:
A forma básica não tem validação e aceita uma chamada com zero argumentos — uma escolha legítima para uma função que é total sobre toda entrada, mas isso significa que a função de linha sozinha é responsável por toda entrada que pode receber. Apenas a forma com
state: aloca e descarta o estado por instrução; a forma apenas com prerun usa PrerunResult<()> e não armazena nada, então não há postrun para ela — uma prerun dessas usa PrerunResult apenas para error e request_buffer_size, nunca para set_state.
Inspecionando tipos de argumento em uma prerun
Como o servidor não valida nada, uma prerun de varargs precisa ver os tipos dos argumentos antes que a primeira linha seja executada.PrerunArgs::type_at fornece essa visão, junto com len()/is_empty() e os métodos de PrerunResult descritos em Estado por instrução.
Combine
is_custom() com custom_name() para aceitar exatamente um tipo personalizado: is_custom() sozinho aceita todo tipo personalizado no servidor.
O exemplo vsql_varargs no repositório do SDK declara uma função por forma. Uma função varargs com estado, validada na prerun e carregando um contador de chamadas por instrução:
str_join ainda faz a correspondência de InValue na função de linha mesmo que a prerun tenha provado que todo argumento é uma string: a prerun vê tipos declarados, não valores, e uma coluna STRING pode carregar NULL em qualquer linha.
describe (uma função apenas com prerun que rejeita zero argumentos e argumentos não escalares e, em seguida, formata uma lista heterogênea de argumentos) e point_path (que valida com is_custom() e custom_name()). Consulte examples/vsql_varargs/src/lib.rs no repositório do SDK Rust.
macro custom_type!
custom_type! registra um novo tipo de coluna. type_name, persisted_length, max_decode_buffer_length, encode, decode e compare são obrigatórios. hash é opcional, mas recomendado. Forneça default ou intrinsic_default_fn, não ambos.
O campo
default não é um valor padrão de coluna; uma coluna omitida é NULL. Ele fornece a string que o servidor codifica ao inicializar o tipo, e isso acontece na primeira vez em que uma instrução nomeia o tipo, e não quando a extensão é instalada. Se você não fornecer nem default nem intrinsic_default_fn, o servidor codifica a string vazia. Em qualquer caso, um encode que retorna Err faz essa instrução falhar e deixa o tipo inutilizável:
default quanto intrinsic_default_fn é recusado quando a extensão é instalada, e não adiado para o primeiro uso.
macro custom!
villagesql::custom!("type_name") referencia um tipo personalizado pelo nome em uma declaração func!:
villagesql::Type::* apareceria em uma lista de parâmetros ou posição de tipo de retorno. A string deve corresponder ao type_name declarado no custom_type! correspondente.
campos do manifest.json
Toda extensão precisa de ummanifest.json ao lado de seu Cargo.toml:
Regras de validação de
name: deve começar com uma letra, terminar com uma letra ou dígito, no máximo 64 caracteres. Um manifesto inválido faz com que INSTALL EXTENSION falhe.