Skip to content
ClickHouse Docs
ClickHouse DocsClickHouse Docs

Funções Definidas pelo Usuário em WebAssembly

O ClickHouse oferece suporte à criação de funções definidas pelo usuário (UDFs) escritas em WebAssembly. Isso permite executar lógica personalizada escrita em linguagens como Rust, C, C++ ou outras, ao compilá-las em módulos WebAssembly.

Sem suporte no ClickHouse Cloud
Recurso experimental

Visão geral

Um módulo WebAssembly é um arquivo binário compilado que contém uma ou mais funções que podem ser chamadas pelo ClickHouse. Pense em um módulo como uma biblioteca ou objeto compartilhado que você carrega uma vez e reutiliza muitas vezes.

Um módulo WebAssembly que contém UDFs pode ser escrito em qualquer linguagem que possa ser compilada para WebAssembly, como Rust, C ou C++.

O código compilado para WebAssembly (código "guest") e executado pelo ClickHouse ("host") roda em um ambiente isolado, com acesso apenas a um espaço de memória dedicado.

O código guest exporta funções que o ClickHouse pode invocar — incluindo as funções que implementam sua lógica personalizada (usadas para definir UDFs), bem como funções de suporte necessárias para o gerenciamento de memória e a troca de dados entre o ClickHouse e o código WebAssembly.

Seu código deve ser compilado para WebAssembly "freestanding" (também conhecido como wasm32-unknown-unknown), sem nenhuma dependência de sistema operacional ou biblioteca padrão. Além disso, apenas o alvo padrão de WebAssembly de 32 bits é compatível (sem a extensão wasm64). O módulo deve seguir um dos protocolos de comunicação (ABIs) compatíveis para interagir com o ClickHouse.

Depois de compilado, o código binário do módulo é carregado no ClickHouse por meio da inserção na tabela system.webassembly_modules. Depois disso, você pode criar UDFs que fazem referência a funções exportadas pelo módulo usando a instrução CREATE FUNCTION ... LANGUAGE WASM.

Pré-requisitos

Ative o suporte a WebAssembly na configuração do ClickHouse:

<clickhouse>
    <allow_experimental_webassembly_udf>true</allow_experimental_webassembly_udf>
    <webassembly_udf_engine>wasmtime</webassembly_udf_engine>
</clickhouse>

Implementações de engine disponíveis:

  • wasmtime (padrão e, atualmente, a única) — usa Wasmtime

Início rápido

Este exemplo demonstra o fluxo completo de criação de uma WebAssembly UDF, implementando uma calculadora da conjectura de Collatz.

Vamos escrever o código no formato WebAssembly Text (WAT), que é uma representação legível do WebAssembly, então não é necessário usar nenhuma linguagem de programação nesta etapa. O ClickHouse exige que o módulo esteja em formato binário, por isso usaremos um transpilador para converter WAT em WASM. Para realizar essa conversão, você pode usar o wat2wasm, do WebAssembly Binary Toolkit (WABT), ou o comando parse, do wasm-tools.

cat << 'EOF' | wasm-tools parse | clickhouse client -q "INSERT INTO system.webassembly_modules (name, code) SELECT 'collatz', code FROM input('code String') FORMAT RawBlob"
(module
  (func $next (param $n i32) (result i32)
    local.get $n i32.const 1 i32.and
    (if (result i32)
      (then local.get $n i32.const 3 i32.mul i32.const 1 i32.add)
      (else local.get $n i32.const 2 i32.div_u)))
  (func $steps (export "steps") (param $n i32) (result i32)
    (local $count i32)
    local.get $n i32.const 1 i32.lt_u
    (if (then i32.const 0 return))
    (block $done (loop $loop
      local.get $n i32.const 1 i32.eq br_if $done
      local.get $n call $next local.set $n
      local.get $count i32.const 1 i32.add local.set $count
      br $loop))
    local.get $count)
)
EOF

No trecho acima, enviamos o código binário WASM diretamente para o ClickHouse client usando FORMAT RawBlob para inseri-lo na tabela system.webassembly_modules.

Em seguida, definimos a UDF que faz referência à função steps exportada pelo módulo:

CREATE FUNCTION collatz_steps LANGUAGE WASM ARGUMENTS (n UInt32) RETURNS UInt32 FROM 'collatz' :: 'steps';

Observe que especificamos o nome da função no módulo após ::, pois ele difere do nome da UDF.

Agora podemos usar a função collatz_steps em nossas consultas:

SELECT groupArray(collatz_steps(number :: UInt32))
FROM numbers(1, 100)
FORMAT TSV

A coluna number é convertida explicitamente para UInt32, porque as funções WebAssembly exigem uma correspondência exata de tipos com a assinatura especificada na instrução CREATE FUNCTION.

No resultado, obtivemos a sequência de etapas de Collatz para números de 1 a 100, correspondente à sequência A006577 da OEIS.

[0,1,7,2,5,8,16,3,19,6,14,9,9,17,17,4,12,20,20,7,7,15,15,10,23,10,111,18,18,18,106,5,26,13,13,21,21,21,34,8,109,8,29,16,16,16,104,11,24,24,24,11,11,112,112,19,32,19,32,19,19,107,107,6,27,27,27,14,14,14,102,22,115,22,14,22,22,35,35,9,22,110,110,9,9,30,30,17,30,17,92,17,17,105,105,12,118,25,25,25]

Gerencie módulos WASM por meio da tabela de sistema

Os módulos WebAssembly são armazenados na tabela system.webassembly_modules, que tem a seguinte estrutura:

  • Colunas
    • name String — Nome do módulo. Não pode estar vazio; apenas caracteres de palavra.
    • code String — Código WASM binário bruto. Somente para escrita; as leituras retornam uma string vazia.
    • hash UInt256 — SHA256 do binário do módulo (zero se estiver presente em disco, mas ainda não tiver sido carregado).

O gerenciamento de módulos é feito por meio de operações SQL padrão nessa tabela:

Inserir um módulo

INSERT INTO system.webassembly_modules (name, code)
SELECT 'my_module', base64Decode('AGFzbQEAAAA...');

Opcionalmente, informe o hash de integridade:

INSERT INTO system.webassembly_modules (name, code, hash)
SELECT 'my_module', base64Decode('...'), reinterpretAsUInt256(unhex('369f...c57d'));

Se o hash fornecido não corresponder ao SHA256 calculado para o código do módulo, a inserção falhará. Isso pode ser útil ao carregar módulos a partir de fontes externas, como S3 ou HTTP.

Distribuir um módulo em um cluster

system.webassembly_modules é uma tabela por instância — um INSERT é gravado apenas na réplica que atende à conexão. Não existe uma forma ON CLUSTER para a instrução INSERT, portanto, um CREATE FUNCTION ... ON CLUSTER subsequente falhará nas réplicas que não têm o módulo:

Code: 674. DB::Exception: WebAssembly module 'collatz' not found:
while adding user defined function `collatz_steps`. (RESOURCE_NOT_FOUND)

Para propagar um insert para todos os nós, grave na função de tabela cluster em vez de na tabela local system.webassembly_modules:

cat collatz.wasm | clickhouse client -q "
  INSERT INTO FUNCTION cluster('default', 'system', 'webassembly_modules') (name, code)
  SELECT 'collatz', code FROM input('code String') FORMAT RawBlob"

Após o insert distribuído, o módulo passa a estar presente em todas as réplicas e CREATE FUNCTION ... ON CLUSTER é executado com sucesso:

CREATE FUNCTION collatz_steps ON CLUSTER 'default'
LANGUAGE WASM FROM 'collatz' :: 'steps'
ARGUMENTS (n UInt32) RETURNS UInt32;

Você pode verificar se o módulo está carregado em todo o cluster com clusterAllReplicas:

SELECT hostName(), name FROM clusterAllReplicas('default', system.webassembly_modules) WHERE name = 'collatz';

As inserções em system.webassembly_modules são idempotentes para o mesmo par (name, hash), portanto executar novamente a inserção distribuída é seguro e é uma forma razoável de reparar o estado depois que uma réplica for substituída. Observe que servidores adicionados recentemente não recebem módulos existentes retroativamente — você deve executar novamente a inserção no cluster atualizado ou colocar o binário no diretório user_scripts/wasm/ no novo host.

Listar módulos

SELECT name, lower(hex(reinterpretAsFixedString(hash))) AS sha256 FROM system.webassembly_modules

Excluir um módulo

A exclusão é feita pela instrução DELETE FROM system.webassembly_modules WHERE name = '...'. O predicado deve ser name = 'literal' para correspondência exata ou name LIKE 'pattern' para excluir todos os módulos cujo nome corresponda ao padrão; nenhum outro formato é aceito.

DELETE FROM system.webassembly_modules WHERE name = 'collatz';

-- Exclusão em massa de todos os módulos cujo nome começa com `tmp_` (o sublinhado literal é escapado como `\_`):
DELETE FROM system.webassembly_modules WHERE name LIKE 'tmp\_%';

Se alguma UDF existente fizer referência a um dos módulos encontrados, a exclusão falhará; portanto, primeiro você deve excluir essas UDFs.

Criar uma WebAssembly UDF

Sintaxe:

CREATE [OR REPLACE] FUNCTION function_name
LANGUAGE WASM
FROM 'module_name' [:: 'source_function_name']
ARGUMENTS ( [name type[, ...]] | [type[, ...]] )
RETURNS return_type
[ABI ROW_DIRECT | ABI BUFFERED_V1 | ABI ASSEMBLYSCRIPT]
[DETERMINISTIC]
[SHA256_HASH 'hex']
[SETTINGS key = value[, ...]];

Parâmetros:

  • function_name: Nome da função no ClickHouse. Pode ser diferente do nome da função exportada no módulo.
  • FROM 'module_name' :: 'source_function_name': Nome do módulo WASM carregado e nome da função no módulo WASM a ser usada (o padrão é function_name)
  • ARGUMENTS: Lista de nomes e tipos de argumentos (os nomes são opcionais e usados em formatos de serialização compatíveis com campos nomeados)
  • ABI: Versão da Application Binary Interface
    • ROW_DIRECT: Mapeamento direto de tipos, processamento linha por linha
    • BUFFERED_V1: Processamento baseado em blocos com serialização
    • ASSEMBLYSCRIPT: Processamento linha por linha para módulos produzidos pelo compilador AssemblyScript. Tipos numéricos são mapeados para tipos primitivos do AssemblyScript; String do ClickHouse é mapeado para string do AssemblyScript.
  • DETERMINISTIC: Declara a função como determinística — sempre retorna a mesma saída para a mesma entrada. Quando especificado, o ClickHouse pode fazer o constant folding de chamadas em que todos os argumentos são constantes: a função é avaliada uma vez durante a análise da consulta, e o resultado é reutilizado para cada linha.
  • SHA256_HASH: Hash esperado do módulo para verificação (preenchido automaticamente se omitido); pode ser usado para garantir que o módulo WASM correto seja carregado em diferentes réplicas.
  • SETTINGS: Configurações por função
    • serialization_format String — Formato usado para serializar blocos de argumentos passados ao módulo e analisar o resultado retornado. Usado apenas por ABI BUFFERED_V1. Valores compatíveis: MsgPack, JSONEachRow, CSV, TSV, TSVRaw, RowBinary e Buffers. Padrão: MsgPack. Formatos baseados em blocos, como Buffers, devem retornar uma única coluna cujo tipo corresponda à assinatura da função declarada.
    • webassembly_udf_enable_fuel Bool — Habilita um orçamento finito de fuel para a função. Padrão: true. Quando false, a configuração no nível da consulta webassembly_udf_max_fuel é ignorada para esta função. Desabilitar os limites de fuel pode melhorar o desempenho. No entanto, para código guest não confiável ou com bugs, isso pode aumentar o risco de execução descontrolada.

Versões de ABI

Para interagir com o ClickHouse, os módulos WebAssembly devem seguir uma das ABIs (Application Binary Interfaces) compatíveis.

  • ROW_DIRECT: Mapeamento direto de tipos (somente tipos primitivos Int32, UInt32, Int64, UInt64, Float32, Float64)
  • BUFFERED_V1: Tipos complexos com serialização
  • ASSEMBLYSCRIPT: Interoperação linha por linha com módulos AssemblyScript; oferece suporte a tipos numéricos e String.

ABI ROW_DIRECT

Chama diretamente uma função WASM exportada para cada linha.

  • Argumentos e tipos de retorno devem ser tipos numéricos Int32/UInt32/Int64/UInt64/Float32/Float64/Int128/UInt128.
  • Strings não são compatíveis com esta ABI.
  • As assinaturas devem corresponder à exportação WASM (i32/i64/f32/f64/v128).
  • O módulo não precisa exportar funções de suporte.

Por exemplo, uma função com a assinatura:

(func (param i32 i64 f32) (result f64) ...)

Pode ser criado da seguinte forma:

CREATE FUNCTION my_func ARGUMENTS (Int32, UInt64, Float32) RETURNS Float64 ...

O WebAssembly não faz distinção entre argumentos com sinal e sem sinal; em vez disso, usa instruções diferentes para interpretar os valores. Assim, o tamanho do argumento deve corresponder exatamente, enquanto o uso de sinal é determinado pelas operações dentro da função.

ABI BUFFERED_V1

Processa blocos inteiros de uma só vez usando (des)serialização por meio da memória WASM. Compatível com quaisquer tipos de argumentos e de retorno.

Os dados são trocados por meio de buffers na memória WASM. Um buffer é uma estrutura de 8 bytes que contém um ponteiro para os dados e o tamanho deles (dois valores u32 little-endian). Os buffers são passados por identificador — um ponteiro para essa estrutura, e não para os próprios dados. O código guest deve exportar duas funções para criar e destruir esses buffers.

Para cada bloco de entrada, o ClickHouse:

  1. Serializa as colunas de argumentos usando o serialization_format da função (MsgPack por padrão). Formatos baseados em linhas gravam os valores linha a linha, com os valores dos argumentos na ordem em que são declarados em ARGUMENTS; formatos com campos nomeados usam os nomes dos argumentos, portanto, declare-os em ARGUMENTS ao usar esses formatos.
  2. Chama clickhouse_create_buffer, exportada pelo módulo, e copia os dados serializados para a memória apontada pelo buffer retornado.
  3. Chama a função definida pelo usuário com dois argumentos i32: o identificador do buffer de entrada (0 se a função não tiver argumentos) e o número de linhas. A função retorna um único i32 — o identificador do buffer de resultado, que o próprio código guest aloca; retornar 0 faz a consulta falhar com um erro.
  4. Lê o buffer de resultado: ele deve conter exatamente uma coluna com exatamente o mesmo número de linhas, serializada no mesmo formato. Para formatos com campos nomeados, como JSONEachRow, a coluna de resultado deve se chamar result.
  5. Chama clickhouse_destroy_buffer nos identificadores dos buffers de entrada (se houver) e de resultado. O código guest não deve liberar o buffer de resultado por conta própria nem retornar o identificador de entrada como resultado — caso contrário, ele seria destruído duas vezes.

A função é invocada uma vez por bloco de entrada: uma consulta grande é dividida em vários blocos pelo pipeline de consulta (o número máximo de linhas por chamada também pode ser limitado pela configuração webassembly_udf_max_input_block_size). Portanto, a contagem de linhas passada em cada chamada corresponde ao tamanho desse bloco, e não ao da consulta inteira.

(module
  ;; Allocate a new buffer of specified size
  ;; Returns: handle to Buffer structure (not direct data pointer!) with pointer to data and size
  (func (export "clickhouse_create_buffer")
    (param $size i32)    ;; Size of data to allocate
    (result i32))        ;; Returns buffer handle with enough space

  ;; Free a buffer by its handle
  (func (export "clickhouse_destroy_buffer")
    (param $handle i32)  ;; Buffer handle to free
    (result))            ;; No return value

    ;; User-defined function
    (func (export "user_defined_function1")
      (param $input_buffer_handle i32)  ;; Input buffer handle
      (param $n i32)                    ;; Number of rows in input
      (result i32))                     ;; Returns output buffer handle
)

Um exemplo completo em C freestanding: str_reverse inverte os bytes de cada string de entrada usando serialization_format = 'RowBinary'. As instâncias do módulo são reutilizadas entre blocos, portanto clickhouse_destroy_buffer precisa realmente liberar a memória — aqui, o allocator é redefinido quando todos os buffers são destruídos.

#include <stdint.h>

typedef struct {
    uint8_t * data;
    uint32_t size;
} ClickHouseBuffer;

#define HEAP_SIZE (1 << 24)
static _Alignas(16) uint8_t heap[HEAP_SIZE];
static uint32_t heap_pos = 0;
static uint32_t live_buffers = 0;

__attribute__((export_name("clickhouse_create_buffer")))
ClickHouseBuffer * clickhouse_create_buffer(uint32_t size)
{
    uint32_t total = (sizeof(ClickHouseBuffer) + size + 15u) & ~15u;
    if (heap_pos + total > HEAP_SIZE)
        return 0; /* a zero handle makes the host fail the query with an error */
    ClickHouseBuffer * buf = (ClickHouseBuffer *) &heap[heap_pos];
    buf->data = &heap[heap_pos + sizeof(ClickHouseBuffer)];
    buf->size = size;
    heap_pos += total;
    ++live_buffers;
    return buf;
}

__attribute__((export_name("clickhouse_destroy_buffer")))
void clickhouse_destroy_buffer(ClickHouseBuffer * buf)
{
    if (--live_buffers == 0)
        heap_pos = 0;
}

/* RowBinary prefixes each String with its byte length as an unsigned varint (LEB128) */
static uint64_t read_varint(const uint8_t ** pp)
{
    uint64_t value = 0;
    for (int shift = 0;; shift += 7)
    {
        uint8_t b = *(*pp)++;
        value |= (uint64_t)(b & 0x7f) << shift;
        if (!(b & 0x80))
            return value;
    }
}

static void write_varint(uint8_t ** pp, uint64_t value)
{
    while (value >= 0x80)
    {
        *(*pp)++ = (uint8_t)(value | 0x80);
        value >>= 7;
    }
    *(*pp)++ = (uint8_t)value;
}

/* Reverses the bytes of each input string */
__attribute__((export_name("str_reverse")))
ClickHouseBuffer * str_reverse(ClickHouseBuffer * input, uint32_t num_rows)
{
    ClickHouseBuffer * out = clickhouse_create_buffer(input->size);
    if (!out)
        return 0;
    const uint8_t * in = input->data;
    uint8_t * o = out->data;
    for (uint32_t row = 0; row < num_rows; ++row)
    {
        uint64_t len = read_varint(&in);
        write_varint(&o, len);
        for (uint64_t i = 0; i < len; ++i)
            o[i] = in[len - 1 - i];
        in += len;
        o += len;
    }
    out->size = (uint32_t)(o - out->data);
    return out;
}

Compile usando clang e wasm-ld (incluídos no LLVM/lld):

clang --target=wasm32 -ffreestanding -nostdlib -fno-builtin -c str_reverse.c
wasm-ld --no-entry str_reverse.o -o str_reverse.wasm

As funções devem estar visíveis nas exportações do módulo: marque-as com __attribute__((export_name("..."))), como acima, ou faça a vinculação com wasm-ld --export-all. -fno-builtin impede que o clang converta loops simples de bytes em chamadas a memcpy/memset, que não estão disponíveis sem uma biblioteca padrão.

Carregue o módulo e crie a função:

cat str_reverse.wasm | clickhouse client -q "INSERT INTO system.webassembly_modules (name, code) SELECT 'str_reverse', code FROM input('code String') FORMAT RawBlob"
CREATE FUNCTION str_reverse LANGUAGE WASM ABI BUFFERED_V1
FROM 'str_reverse' :: 'str_reverse'
ARGUMENTS (s String) RETURNS String
SETTINGS serialization_format = 'RowBinary';

SELECT str_reverse(toString(number + 100)) FROM numbers(3);
001
101
201

ABI ASSEMBLYSCRIPT

Destina-se a módulos produzidos pelo compilador AssemblyScript. Cada linha aciona uma chamada para a função exportada, mapeando valores do ClickHouse para tipos primitivos do AssemblyScript e objetos string.

Tipos compatíveis:

  • Numéricos: Int8/UInt8, Int16/UInt16 (ampliados para i32 no limite), Int32/UInt32, Int64/UInt64, Float32, Float64

  • String — corresponde a string do AssemblyScript (UTF-16 na memória WASM). O ClickHouse lida automaticamente com a conversão UTF-8 ↔ UTF-16.

  • Classes personalizadas do AssemblyScript não são compatíveis como tipos de argumento ou de retorno — seus IDs de classe do runtime não são estáveis entre compilações (consulte AssemblyScript#2982).

Requisitos do módulo:

O módulo deve ser compilado com o runtime gerenciado do AssemblyScript para que __new, __pin e __unpin sejam exportados. O tratamento padrão de strings de entrada e saída pressupõe isso. A invocação recomendada:

asc src.ts --runtime incremental --exportRuntime -o src.wasm

O AssemblyScript também importa env.abort para traps de runtime (falta de memória, verificações de limites etc.). O ClickHouse fornece essa importação automaticamente: quando um abort é disparado, a consulta ativa falha com uma exceção WASM_ERROR que inclui a mensagem decodificada do AssemblyScript e o local no código-fonte.

Exemplo:

// src.ts
export function add(a: u32, b: u32): u32 {
  return a + b;
}

export function greet(name: string): string {
  return "Hello, " + name + "!";
}

Após compilar com asc e carregar o .wasm resultante em system.webassembly_modules, declare as UDFs da seguinte forma:

CREATE FUNCTION as_add
    LANGUAGE WASM ABI ASSEMBLYSCRIPT
    FROM 'as_example' :: 'add'
    ARGUMENTS (a UInt32, b UInt32) RETURNS UInt32;

CREATE FUNCTION as_greet
    LANGUAGE WASM ABI ASSEMBLYSCRIPT
    FROM 'as_example' :: 'greet'
    ARGUMENTS (name String) RETURNS String;

Observação sobre o desenvolvimento de UDFs em Rust

Para programas em Rust, fornecemos um crate auxiliar clickhouse-wasm-udf para simplificar o desenvolvimento de WebAssembly UDFs para o ClickHouse. O crate fornece funções para gerenciamento de memória, então você não precisa implementar manualmente as funções clickhouse_create_buffer e clickhouse_destroy_buffer; basta adicionar o crate como dependência. Também há macros #[clickhouse_wasm_udf] para encapsular suas funções Rust comuns no formato ABI exigido.

Com o crate, você pode escrever UDFs assim:


use clickhouse_wasm_udf_bindgen::clickhouse_udf;

#[clickhouse_udf]
pub fn some_udf(data: String) -> HashMap<String, String> {
    // Sua implementação aqui
}

Macros geram uma função wrapper que aceita e retorna estruturas de buffer e lida automaticamente com a serialização/desserialização usando serde.

API do host disponível para módulos

As funções de host a seguir podem ser importadas e usadas por módulos:

  • clickhouse_server_version() -> i64 — retorna a versão do ClickHouse server como um inteiro (por exemplo, 25011001 para v25.11.1.1).
  • clickhouse_throw(ptr: i32, size: i32) — gera um erro com a mensagem fornecida. Aceita um ponteiro para a posição de memória que contém a string da mensagem de erro e o tamanho da string.
  • clickhouse_log(ptr: i32, size: i32) — registra uma mensagem no log de texto do ClickHouse server.
  • clickhouse_random(ptr: i32, size: i32) — preenche a memória com bytes aleatórios.
  • env.abort(message: i32, fileName: i32, line: i32, column: i32) — fornecido para módulos compatíveis com AssemblyScript. Chamá-lo (ou disparar uma trap de runtime do AssemblyScript que o chame) encerra a UDF com uma exceção WASM_ERROR contendo a mensagem decodificada e a localização no código-fonte. Módulos que não importam env.abort não são afetados.

Configurações

As configurações a seguir, no nível da consulta, controlam a execução de UDFs em WebAssembly:

  • webassembly_udf_max_fuel — Limite de fuel por execução de uma instância de UDF em WebAssembly. Cada instrução de WebAssembly consome uma certa quantidade de fuel. O valor é escalado por 1024 antes de ser passado ao runtime, portanto webassembly_udf_max_fuel = 1 corresponde a aproximadamente 1024 unidades de fuel. Defina como 0 para não haver limite finito. Aplica-se apenas a funções cuja configuração por função webassembly_udf_enable_fuel seja true, que é o padrão.

  • webassembly_udf_max_memory — Limite de memória, em bytes, por instância de UDF em WebAssembly.

  • webassembly_udf_max_input_block_size — Número máximo de linhas passadas para uma UDF em WebAssembly em um único bloco. Defina como 0 para processar todas as linhas de uma só vez.

  • webassembly_udf_max_instances — Número máximo de instâncias de UDF em WebAssembly que podem ser executadas em paralelo por função.

Exemplo de uso:

SET webassembly_udf_max_fuel = 200000;
SELECT my_wasm_udf(column) FROM table;

Veja também

Navigation