Pular para o conteúdo principal
EntradaSaídaAlias

Descrição

A especificação oficial completa do formato Native está disponível aqui, e a especificação complementar do protocolo Native — o protocolo TCP wire que o transporta — está disponível aqui.
Ambas as especificações foram geradas por LLMs a partir do código-fonte do ClickHouse. O código continua sendo a principal fonte de referência: quando a especificação e o código divergem, o código está correto.
O formato Native é o formato mais eficiente do ClickHouse porque é de fato “colunar”, ou seja, não converte colunas em linhas. Nesse formato, os dados são gravados e lidos em blocos, em formato binário. Para cada bloco, são registrados, um após o outro, o número de linhas, o número de colunas, os nomes e tipos das colunas e as partes das colunas no bloco. Esse é o formato usado na interface nativa para a interação entre servidores, no cliente de linha de comando e em clientes C++.
Você pode usar esse formato para gerar rapidamente dumps que só podem ser lidos pelo SGBD ClickHouse. Talvez não seja prático trabalhar diretamente com esse formato.

Formato wire dos tipos de dados

Os dados são enviados via wire em formato colunar, o que significa que cada coluna é enviada separadamente, e todos os valores de uma coluna são enviados juntos como um único array. Cada coluna em um bloco contém um cabeçalho semelhante a RowBinaryWithNamesAndTypes.
Ao usar o protocolo binário TCP nativo (ou quando o endpoint HTTP recebe ?client_protocol_version=<n>), uma estrutura BlockInfo é gravada antes das contagens de colunas e linhas. Os exemplos nesta seção usam a interface HTTP simples, sem versão de protocolo, o que omite BlockInfo.

Estrutura do bloco

A consulta a seguir retorna duas colunas, number e str, com três linhas:
Os dados de saída cabem em um único bloco do ClickHouse e terão esta aparência:

Múltiplos blocos

No entanto, em muitos casos, os dados não cabem em um único bloco, e o ClickHouse os enviará em vários blocos. Considere a seguinte consulta, que busca duas linhas com o tamanho do bloco reduzido para forçar a divisão dos dados em uma linha por bloco:
A saída:

Tipos de dados simples

O formato wire de um valor individual de um dos tipos de dados mais simples é semelhante ao de RowBinary/RowBinaryWithNamesAndTypes. A lista completa de tipos que correspondem a essa descrição inclui:
  • (U)Int8, (U)Int16, (U)Int32, (U)Int64, (U)Int128, (U)Int256
  • Float32, Float64
  • Bool
  • String
  • FixedString(N)
  • Date
  • Date32
  • DateTime
  • DateTime64
  • IPv4
  • IPv6
  • UUID
Consulte as descrições dos tipos acima em “formato wire dos tipos de dados do RowBinary” para obter mais detalhes.

Tipos de dados complexos

A codificação dos tipos abaixo é diferente da de RowBinary e RowBinaryWithNamesAndTypes.
  • Nullable
  • LowCardinality
  • Array
  • Map
  • Variant
  • Dynamic
  • JSON

Nullable

No formato Native, uma coluna Nullable terá uma quantidade de bytes igual ao número de linhas no bloco antes dos dados propriamente ditos. Cada um desses bytes indica se o valor é NULL ou não. Por exemplo, com esta consulta, cada número ímpar será NULL:
A saída será assim:
Funciona de forma semelhante com Nullable(String). O indicador de nulo sempre vem do byte da máscara de nulabilidade — um valor de máscara 0x01 significa que a linha é NULL, independentemente do conteúdo da string. Para linhas NULL, a string subjacente é armazenada como uma string vazia (comprimento LEB128 0). Observe que uma string vazia não NULL também tem comprimento LEB128 0; portanto, apenas o byte da máscara distingue os dois casos. Por exemplo, a seguinte consulta:
A saída será assim:

LowCardinality

Diferentemente do RowBinary, em que LowCardinality é transparente, o formato Native usa uma codificação colunar baseada em dicionário. Uma coluna é codificada com um prefixo de versão, seguido de um dicionário de valores únicos e de um array de índices inteiros nesse dicionário.
Uma coluna pode ser definida como LowCardinality(Nullable(T)), mas não é possível defini-la como Nullable(LowCardinality(T)) — isso sempre resultará em um erro do servidor.
O prefixo de versão é um UInt64(LE) com valor 1, gravado uma vez por coluna. Em seguida, por bloco, é gravado o seguinte:
  • UInt64(LE) — campo de bits IndexesSerializationType. Os bits 0–7 codificam a largura do índice (0 = UInt8, 1 = UInt16, 2 = UInt32, 3 = UInt64). O bit 8 (NeedGlobalDictionaryBit) nunca é definido no formato Native (o servidor gera uma exceção se ele for encontrado). O bit 9 indica que há chaves adicionais de dicionário. O bit 10 indica que o dicionário deve ser reinicializado.
  • UInt64(LE) — número de chaves do dicionário, seguido pelas chaves serializadas em lote usando a codificação do tipo interno.
  • UInt64(LE) — número de linhas, seguido pelos valores de índice serializados em lote usando a largura UInt apropriada.
O dicionário sempre contém um valor padrão no índice 0 (por exemplo, string vazia para String, 0 para tipos numéricos). Para LowCardinality(Nullable(T)), o índice 0 representa NULL, e as chaves são serializadas sem o wrapper Nullable. Por exemplo, LowCardinality(String) com 5 linhas ['foo', 'bar', 'baz', 'foo', 'bar']:
Com LowCardinality(Nullable(String)), o índice 0 é NULL:

Array

Ao contrário de RowBinary, em que cada array é precedido por uma contagem de elementos em LEB128, o formato Native codifica arrays como dois subfluxos colunares:
  • N offsets cumulativos UInt64 (little-endian, 8 bytes cada). A linha i tem offset[i] - offset[i-1] elementos, com offset[-1] implicitamente igual a 0.
  • Todos os elementos aninhados de todas as linhas, serializados em bloco de forma contígua.
Por exemplo, Array(UInt32) com 3 linhas [[0, 10], [1, 11], [2, 12]]:
Um array vazio tem o mesmo offset da linha anterior. Por exemplo, Array(String) com 4 linhas [[], ['0'], ['0','1'], ['0','1','2']]:

Map

Um Map(K, V) é codificado como Array(Tuple(K, V)) — offsets do array seguidos por todas as chaves e, em seguida, todos os valores. Isso difere de RowBinary, em que chaves e valores são intercalados em cada entrada. Por exemplo, Map(String, UInt64) com 3 linhas [{'a':0,'b':10}, {'a':1,'b':11}, {'a':2,'b':12}]:

Variant

Ao contrário de RowBinary, em que cada linha traz seu próprio byte discriminante seguido do valor inline, o formato Native separa os discriminantes dos dados.
Assim como no RowBinary, os tipos na definição são sempre ordenados alfabeticamente, e o discriminante é o índice nessa lista ordenada. 0xFF (255) representa NULL.
Uma coluna Variant é codificada da seguinte forma:
  • Prefixo do modo dos discriminantes UInt64(LE) (0 = BASIC, 1 = COMPACT). A saída do formato Native normalmente usa BASIC (0); o modo COMPACT pode aparecer ao ler dados armazenados com use_compact_variant_discriminators_serialization ativado.
  • N discriminantes UInt8, um por linha.
  • Os dados de cada tipo variante em uma coluna em bloco separada, contendo apenas as linhas correspondentes, na ordem dos discriminantes.
Por exemplo, Variant(String, UInt32) com 5 linhas [0::UInt32, 'hello', NULL, 3::UInt32, 'hello'] (ordenado: String = 0, UInt32 = 1):

Dynamic

Ao contrário de RowBinary, em que cada valor é autodescritivo (prefixo do tipo + valor), o formato Native serializa Dynamic como um prefixo de estrutura seguido de uma coluna Variant. O prefixo de estrutura contém uma versão de serialização UInt64(LE), seguida pelo número de tipos dinâmicos (como VarUInt) e, então, pelos nomes dos tipos como strings. Na versão V1, a contagem de tipos é gravada duas vezes por compatibilidade. Os dados que vêm na sequência formam uma coluna Variant cuja lista de tipos inclui os tipos dinâmicos e um tipo interno SharedVariant, ordenados alfabeticamente. Por exemplo, Dynamic com 5 linhas [0::UInt32, 'hello', NULL, 3::UInt32, 'hello']:

JSON

Ao contrário do RowBinary, em que cada linha é autodescritiva com nomes de caminhos e valores, o formato Native serializa JSON em uma estrutura colunar. A codificação é complexa e depende da versão: consiste em um prefixo de estrutura com a versão de serialização, nomes de caminhos dinâmicos e o layout de dados compartilhados, seguido por caminhos tipados (cada um como uma coluna em bloco), caminhos dinâmicos (cada um como uma coluna Dynamic) e dados compartilhados para caminhos de overflow. Para uma interoperabilidade mais simples, considere usar a configuração output_format_native_write_json_as_string=1, que serializa colunas JSON como strings simples de texto JSON (uma String por linha).
Última modificação em 2 de julho de 2026