> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-parallel-read-in-order-multi-part.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> O cliente C# oficial para se conectar ao ClickHouse.

# Cliente C# do ClickHouse

export const Image = ({img, alt, size = "lg", background}) => {
  const normalizedSize = ["sm", "md", "lg"].includes(size) ? size : "lg";
  const backgroundColor = background === "white" ? "white" : background === "black" ? "rgb(31 31 28)" : undefined;
  return <div className={`ch-image-${normalizedSize}`}>
      <Frame>
        <img src={img} alt={alt} style={{
    backgroundColor
  }} />
      </Frame>
    </div>;
};

O cliente C# oficial para se conectar ao ClickHouse.
O código-fonte do cliente está disponível no [repositório do GitHub](https://github.com/ClickHouse/clickhouse-cs).
Desenvolvido originalmente por [Oleg V. Kozlyuk](https://github.com/DarkWanderer).

A biblioteca fornece duas APIs principais:

* **`ClickHouseClient`** (recomendado): um cliente de alto nível, thread-safe, projetado para uso como singleton. Fornece uma API assíncrona simples para consultas e inserções em massa. Ideal para a maioria das aplicações.

* **ADO.NET** (`ClickHouseDataSource`, `ClickHouseConnection`, `ClickHouseCommand`): abstrações padrão de banco de dados do .NET. Necessário para integração com ORM (Dapper, Linq2db) e quando você precisa de compatibilidade com ADO.NET. `ClickHouseBulkCopy` é uma classe auxiliar para inserir dados com eficiência usando uma conexão ADO.NET. `ClickHouseBulkCopy` foi descontinuado e será removido em um lançamento futuro; use `ClickHouseClient.InsertBinaryAsync` no lugar.

Ambas as APIs compartilham o mesmo pool de conexões HTTP subjacente e podem ser usadas juntas na mesma aplicação.

<h2 id="migration-guide">
  Guia de migração
</h2>

1. Atualize o arquivo `.csproj` com o novo nome do pacote `ClickHouse.Driver` e [a versão mais recente no NuGet](https://www.nuget.org/packages/ClickHouse.Driver).
2. Atualize todas as referências a `ClickHouse.Client` para `ClickHouse.Driver` no seu código.

***

<h2 id="supported-net-versions">
  Versões compatíveis do .NET
</h2>

`ClickHouse.Driver` oferece suporte às seguintes versões do .NET:

* .NET 6.0
* .NET 8.0
* .NET 9.0
* .NET 10.0

<h2 id="supported-clickhouse-versions">
  Versões compatíveis do ClickHouse
</h2>

O cliente oferece suporte oficial aos 3 lançamentos mais recentes, além dos 2 lançamentos LTS mais recentes.

<h2 id="installation">
  Instalação
</h2>

Instale o pacote via NuGet:

```bash theme={null}
dotnet add package ClickHouse.Driver
```

Ou use o Gerenciador de Pacotes NuGet:

```bash theme={null}
Install-Package ClickHouse.Driver
```

<h2 id="quick-start">
  Início rápido
</h2>

```csharp theme={null}
using ClickHouse.Driver;

// Cria um cliente (geralmente como um singleton)
using var client = new ClickHouseClient("Host=my.clickhouse;Protocol=https;Port=8443;Username=user");

// Executa uma consulta
var version = await client.ExecuteScalarAsync("SELECT version()");
Console.WriteLine(version);
```

<h2 id="configuration">
  Configuração
</h2>

Há duas formas de configurar sua conexão com o ClickHouse:

* **String de conexão:** pares de chave/valor separados por ponto e vírgula que especificam o host, as credenciais de autenticação e outras opções de conexão.
* **Objeto `ClickHouseClientSettings`:** um objeto de configuração fortemente tipado que pode ser carregado de arquivos de configuração ou definido no código.

Abaixo está a lista completa de todas as configurações, seus valores padrão e seus efeitos.

<h3 id="connection-settings">
  Configurações de conexão
</h3>

| Propriedade | Tipo | Padrão | Chave da string de conexão | Descrição |
| - | - | - | - | - |
| Host | `string` | `"localhost"` | `Host` | Nome do host ou endereço IP do servidor do ClickHouse |
| Port | `ushort` | 8123 (HTTP) / 8443 (HTTPS) | `Port` | Número da porta; o padrão depende do protocolo |
| Username | `string` | `"default"` | `Username` | Nome de usuário para authentication |
| Password | `string` | `""` | `Password` | Senha de authentication |
| Database | `string` | `""` | `Database` | Banco de dados padrão; se vazio, usa o padrão do servidor/usuário |
| Protocol | `string` | `"http"` | `Protocol` | Protocolo de conexão: `"http"` ou `"https"` |
| Path | `string` | `null` | `Path` | Caminho da URL para cenários com reverse proxy (por exemplo, `/clickhouse`) |
| Timeout | `TimeSpan` | 2 minutos | `Timeout` | Tempo limite da operação (armazenado como segundos na string de conexão) |

<h3 id="data-format-serialization">
  Formato e serialização de dados
</h3>

| Propriedade | Tipo | Padrão | Chave da string de conexão | Descrição |
| - | - | - | - | - |
| UseCompression | `bool` | `true` | `Compression` | Controla a compressão de transporte em ambas as direções para uma consulta comum: solicita ao servidor que comprima a resposta (`enable_http_compression`; consulte `AcceptEncoding` para o codec, que um valor explícito pode solicitar mesmo com esta opção desativada) **e** comprime o corpo da requisição com gzip — exceto com `UseFormDataParameters`, cujo corpo multipart é sempre enviado sem compressão. Inserts binários nunca a consultam; eles usam `InsertOptions.Compressor` — consulte [compressão na inserção](#insert-compression) |
| AcceptEncoding | `string` | `null` | `AcceptEncoding` | `Accept-Encoding` enviado em cada requisição, substituindo os codecs que o driver anuncia por padrão (`zstd, lz4, gzip, deflate`). O que quer que o servidor responda é decodificado de forma transparente. Consulte [Descompressão da resposta](#response-decompression) |
| UseCustomDecimals | `bool` | `true` | `UseCustomDecimals` | Usa `ClickHouseDecimal` para precisão arbitrária; se `false`, usa o `decimal` do .NET (limite de 128 bits) |
| ReadStringsAsByteArrays | `bool` | `false` | `ReadStringsAsByteArrays` | Lê colunas `String` e `FixedString` como `byte[]` em vez de `string`; útil para dados binários |
| UseFormDataParameters | `bool` | `false` | `UseFormDataParameters` | Envia parâmetros como form data em vez de string de consulta da URL |
| ReadBufferSize | `int` | `65536` (64 KiB) | `ReadBufferSize` | Tamanho em bytes do buffer que lê as respostas HTTP de consultas. O driver aluga o buffer de um pool compartilhado e o devolve ao descartar o leitor, portanto não há uma alocação a cada consulta. Aumente-o para reduzir o reabastecimento do buffer em grandes result sets. O driver mantém um buffer para cada leitor concorrente, portanto o uso de memória cresce com o tamanho do buffer e com o número de leitores concorrentes. Consulte [Buffers](#perf-buffers). |
| ParameterTypeResolver | `IParameterTypeResolver` | `null` | — | Resolver personalizado para mapeamento de tipo de parâmetro no estilo `@`; consulte [Mapeamento personalizado de tipo de parâmetro](#parameter-type-mapping) |
| ParameterFormatter | `IParameterFormatter` | `null` | — | Formatador personalizado para serialização de valores de parâmetros; consulte [Formatação personalizada de valores de parâmetros](#parameter-value-formatting) |
| ReadValueConverter | `IReadValueConverter` | `null` | — | Transformação personalizada aplicada aos valores retornados pelo leitor de dados; consulte [Conversão personalizada de valores lidos](#read-value-conversion) |
| JsonReadMode | `JsonReadMode` | `Binary` | `JsonReadMode` | Como os dados JSON são retornados: `Binary` (retorna `JsonObject`) ou `String` (retorna a string JSON bruta) |
| JsonWriteMode | `JsonWriteMode` | `String` | `JsonWriteMode` | Como os dados JSON são enviados: `String` (serializa via `JsonSerializer`, aceita todas as entradas) ou `Binary` (somente POCOs registrados com type hints) |
| MapReadMode | `MapReadMode` | `Dictionary` | `MapReadMode` | Como os dados `Map(K, V)` são retornados: `Dictionary` (retorna `Dictionary<K, V>`; uma chave repetida mantém apenas seu último valor) ou `KeyValuePairs` (retorna `List<KeyValuePair<K, V>>`, mantendo todos os pares). Consulte [Tipo map](#type-map-reading-map) |
| AllowDuplicateJsonKeys | `bool` | `false` | `AllowDuplicateJsonKeys` | Como ler uma linha `JSON` em que ambos os paths sobrepostos contêm um valor. `false` lança uma exceção, pois manter um dos valores significa descartar o outro; `true` mantém o último valor presente na linha. Consulte [Paths sobrepostos](#type-map-reading-json) |

<h3 id="session-management">
  Gerenciamento de sessão
</h3>

| Propriedade | Tipo | Padrão | Chave da string de conexão | Descrição |
| - | - | - | - | - |
| UseSession | `bool` | `false` | `UseSession` | Habilita sessões com estado; serializa solicitações |
| SessionId | `string` | `null` | `SessionId` | ID da sessão; gera automaticamente um GUID se for `null` e `UseSession` for `true` |

<Note>
  O sinalizador `UseSession` habilita a persistência da sessão do servidor, permitindo usar instruções `SET` e tabelas temporárias. As sessões serão redefinidas após 60 segundos de inatividade (timeout padrão). A duração da sessão pode ser estendida definindo configurações de sessão por meio de instruções do ClickHouse ou da configuração do servidor.

  A classe `ClickHouseConnection` normalmente permite operação paralela (várias threads podem executar consultas concorrentemente). No entanto, habilitar o sinalizador `UseSession` limitará isso a uma consulta ativa por conexão a qualquer momento (esta é uma limitação do servidor).
</Note>

<h3 id="security">
  Segurança
</h3>

| Propriedade | Tipo | Padrão | Chave da string de conexão | Descrição |
| - | - | - | - | - |
| SkipServerCertificateValidation | `bool` | `false` | — | Ignora a validação do certificado HTTPS; **não use em produção** |

<h3 id="http-client-configuration">
  Configuração do cliente HTTP
</h3>

| Propriedade | Tipo | Padrão | Chave da string de conexão | Descrição |
| - | - | - | - | - |
| HttpClient | `HttpClient` | `null` | — | Instância personalizada de HttpClient pré-configurada |
| HttpClientFactory | `IHttpClientFactory` | `null` | — | Fábrica personalizada para criar instâncias de HttpClient |
| HttpClientName | `string` | `null` | — | Nome usado pelo HttpClientFactory para criar um cliente específico |

<h3 id="logging-debugging">
  Logging e depuração
</h3>

| Propriedade | Tipo | Padrão | Chave da string de conexão | Descrição |
| - | - | - | - | - |
| LoggerFactory | `ILoggerFactory` | `null` | — | Fábrica de loggers para diagnóstico |
| EnableDebugMode | `bool` | `false` | — | Habilita o rastreamento de rede do .NET (requer LoggerFactory com o nível definido como Trace); **impacto significativo no desempenho** |

<h3 id="custom-settings-roles">
  Configurações personalizadas e roles
</h3>

| Propriedade | Tipo | Padrão | Chave da string de conexão | Descrição |
| - | - | - | - | - |
| CustomSettings | `IDictionary<string, object>` | Vazio | prefixo `set_*` | Configurações do servidor ClickHouse; veja a observação abaixo |
| Roles | `IReadOnlyList<string>` | Vazio | `Roles` | Roles do ClickHouse separadas por vírgulas (por exemplo, `Roles=admin,reader`) |
| ApplicationInfo | `IReadOnlyDictionary<string, string>` | Vazio | — | Tags de formato livre anexadas ao cabeçalho HTTP `User-Agent` para atribuição de consultas por aplicação. |

<Note>
  Ao usar uma string de conexão para definir configurações personalizadas, use o prefixo `set_`, por exemplo, "set\_max\_threads=4". Ao usar um objeto ClickHouseClientSettings, não use o prefixo `set_`.

  Para ver a lista completa de configurações disponíveis, consulte [aqui](/pt-BR/reference/settings/session-settings).
</Note>

***

<h3 id="connection-string-examples">
  Exemplos de string de conexão
</h3>

<h4 id="basic-connection">
  Conexão básica
</h4>

```text theme={null}
Host=localhost;Port=8123;Username=default;Password=secret;Database=mydb
```

<h4 id="with-custom-clickhouse-settings">
  Com configurações personalizadas do ClickHouse
</h4>

```text theme={null}
Host=localhost;set_max_threads=4;set_readonly=1;set_max_memory_usage=10000000000
```

***

<h3 id="query-options">
  QueryOptions
</h3>

`QueryOptions` permite substituir configurações do cliente individualmente para cada consulta. Todas as propriedades são opcionais e só substituem os padrões do cliente quando especificadas.

| Propriedade | Tipo | Descrição |
| - | - | - |
| QueryId | `string` | Identificador personalizado da consulta para rastreamento em `system.query_log` ou cancelamento |
| Database | `string` | Substitui o banco de dados padrão desta consulta |
| Roles | `IReadOnlyList<string>` | Substitui os roles do cliente para esta consulta |
| CustomSettings | `IDictionary<string, object>` | Configurações do servidor ClickHouse para esta consulta (por exemplo, `max_threads`) |
| CustomHeaders | `IDictionary<string, string>` | Cabeçalhos HTTP adicionais para esta consulta |
| UseSession | `bool?` | Substitui o comportamento da sessão para esta consulta |
| SessionId | `string` | ID da sessão para esta consulta (requer `UseSession = true`) |
| BearerToken | `string` | Substitui o token de autenticação para esta consulta |
| ParameterTypeResolver | `IParameterTypeResolver` | Substitui o resolver do cliente para o mapeamento de tipo de parâmetro no estilo `@`; consulte [Mapeamento personalizado de tipo de parâmetro](#parameter-type-mapping) |
| ParameterFormatter | `IParameterFormatter` | Substitui o formatador do cliente para a serialização do valor do parâmetro no estilo `@`; consulte [Formatação personalizada do valor do parâmetro](#parameter-value-formatting) |
| ReadValueConverter | `IReadValueConverter` | Substitui a transformação no nível do cliente aplicada aos valores retornados pelo leitor de dados; consulte [Conversão personalizada de valor lido](#read-value-conversion) |
| MaxExecutionTime | `TimeSpan?` | Timeout da consulta no servidor (passado como configuração `max_execution_time`); o servidor cancela a consulta se esse limite for excedido |
| AcceptEncoding | `string` | Substituição de `Accept-Encoding` por consulta (por exemplo, `"br"`, `"identity"`), que tem precedência sobre `ClickHouseClientSettings.AcceptEncoding`; também força `enable_http_compression=1` na URL. Consulte [Compactação de transporte por consulta](#per-query-accept-encoding). |

**Exemplo:**

```csharp theme={null}
var options = new QueryOptions
{
    QueryId = "report-2024-001",
    Database = "analytics",
    CustomSettings = new Dictionary<string, object>
    {
        { "max_threads", 4 },
        { "max_memory_usage", 10_000_000_000 }
    },
    MaxExecutionTime = TimeSpan.FromMinutes(5)
};

var reader = await client.ExecuteReaderAsync(
    "SELECT * FROM large_table",
    parameters: null,
    options: options
);
```

***

<h3 id="insert-options">
  InsertOptions
</h3>

`InsertOptions` estende `QueryOptions` com configurações específicas para operações de inserção em massa via `InsertBinaryAsync`.

| Property | Type | Default | Description |
| - | - | - | - |
| BatchSize | `int` | 100,000 | Número de linhas por lote |
| MaxDegreeOfParallelism | `int` | 1 | Número de envios paralelos de lotes |
| Format | `RowBinaryFormat` | `RowBinary` | Formato binário: `RowBinary` ou `RowBinaryWithDefaults` |
| Compressor | `IClickHouseCompressor` | `ZstdCompressor.Default` | Codec aplicado ao corpo da inserção (`Content-Encoding`). `null` envia sem compressão. Veja [Compressão na inserção](#insert-compression) |
| QueryPlacement | `InsertQueryPlacement` | `Body` | Onde a instrução `INSERT INTO ... FORMAT ...` é enviada: `Body` (antes das linhas) ou `Url` (como o parâmetro de URL `query`). Veja [Posicionamento da consulta de inserção](#insert-query-placement) |
| ColumnTypes | `IReadOnlyDictionary<string, string>` | `null` | Nome da coluna → string do tipo ClickHouse. Ignora a consulta de sondagem do esquema quando definido. |
| UseSchemaCache | `bool` | `false` | Mantém em cache o esquema completo da tabela para cada (banco de dados, tabela) durante toda a vida útil do cliente. |

Todas as propriedades de `QueryOptions` também estão disponíveis em `InsertOptions`.

**Exemplo:**

```csharp theme={null}
var insertOptions = new InsertOptions
{
    BatchSize = 50_000,
    MaxDegreeOfParallelism = 4,
    QueryId = "bulk-import-001"
};

long rowsInserted = await client.InsertBinaryAsync(
    "my_table",
    columns,
    rows,
    insertOptions
);
```

<h4 id="skip-schema-query">
  Ignorando a consulta de sondagem do esquema
</h4>

Por padrão, `InsertBinaryAsync` envia uma consulta `SELECT ... WHERE 1=0` antes de cada inserção para identificar os tipos das colunas. Em cenários de alta taxa de transferência, você pode eliminar essa sobrecarga de duas formas:

**Opção 1: Informe explicitamente os tipos das colunas**

Quando você conhece o esquema da tabela em tempo de compilação, passe-o diretamente por meio de `ColumnTypes`. Nenhuma consulta de esquema é enviada:

```csharp theme={null}
var options = new InsertOptions
{
    ColumnTypes = new Dictionary<string, string>
    {
        ["id"] = "UInt64",
        ["name"] = "Nullable(String)",
        ["score"] = "Float32",
    },
};

await client.InsertBinaryAsync("my_table", ["id", "name", "score"], rows, options);
```

**Opção 2: Armazene o esquema em cache**

Ao inserir repetidamente na mesma tabela, defina `UseSchemaCache = true` para consultar o esquema uma única vez e reutilizá-lo nas inserções subsequentes na mesma instância do `ClickHouseClient`:

```csharp theme={null}
var options = new InsertOptions { UseSchemaCache = true };

// Primeira chamada busca o esquema do servidor
await client.InsertBinaryAsync("my_table", columns, batch1, options);

// Segunda chamada reutiliza o esquema em cache — sem round-trip adicional
await client.InsertBinaryAsync("my_table", columns, batch2, options);
```

<Note>
  * `ColumnTypes` tem prioridade sobre `UseSchemaCache`. Se ambos estiverem definidos, os tipos explícitos serão usados.
  * O cache de esquema não detecta alterações feitas com `ALTER TABLE`. Se você modificar o esquema da tabela, crie um novo `ClickHouseClient` ou evite usar `UseSchemaCache` para essa tabela.
  * O cache tem escopo na instância de `ClickHouseClient` e é indexado por (banco de dados, tabela). Diferentes subconjuntos de colunas da mesma tabela compartilham um único esquema em cache.
</Note>

<h2 id="clickhouse-client">
  ClickHouseClient
</h2>

`ClickHouseClient` é a API recomendada para interagir com o ClickHouse. Ela é thread-safe, foi projetada para uso como singleton e gerencia internamente um pool de conexões HTTP.

<h3 id="creating-a-client">
  Criando um cliente
</h3>

Crie um `ClickHouseClient` com uma string de conexão ou um objeto `ClickHouseClientSettings`. Consulte a seção [Configuração](#configuration) para conhecer as opções disponíveis.

Os detalhes do seu serviço do ClickHouse Cloud estão disponíveis no console do ClickHouse Cloud.

Selecione um serviço e clique em **Connect**:

<Image img="https://mintcdn.com/private-7c7dfe99-parallel-read-in-order-multi-part/GTkpPcjoRQ_okrH3/images/_snippets/cloud-connect-button.webp?fit=max&auto=format&n=GTkpPcjoRQ_okrH3&q=85&s=d059c1bbcc7317ff8df85b20189e65f4" size="md" alt="Botão Connect do serviço do ClickHouse Cloud" border width="998" height="932" data-path="images/_snippets/cloud-connect-button.webp" />

Escolha **C#**. Os detalhes da conexão são exibidos abaixo.

<Image img="https://mintcdn.com/private-7c7dfe99-parallel-read-in-order-multi-part/GTkpPcjoRQ_okrH3/images/_snippets/connection-details-csharp.webp?fit=max&auto=format&n=GTkpPcjoRQ_okrH3&q=85&s=487b14816a8a8711d46ae022d82d74ef" size="md" alt="Detalhes da conexão do ClickHouse Cloud para C#" border width="851" height="805" data-path="images/_snippets/connection-details-csharp.webp" />

Se você estiver usando ClickHouse autogerenciado, os detalhes da conexão serão definidos pelo administrador do ClickHouse.

Usando uma string de conexão:

```csharp theme={null}
using ClickHouse.Driver;

using var client = new ClickHouseClient("Host=localhost;Username=default;Password=secret");
```

Ou use `ClickHouseClientSettings`:

```csharp theme={null}
using ClickHouse.Driver;

var settings = new ClickHouseClientSettings
{
    Host = "localhost",
    Username = "default",
    Password = "secret"
};
using var client = new ClickHouseClient(settings);
```

Para cenários com injeção de dependência, use `IHttpClientFactory`:

```csharp theme={null}
// In your DI configuration. No AutomaticDecompression needed — the driver decodes
// compressed responses itself, and a mask here would widen its Accept-Encoding.
services.AddHttpClient("ClickHouse", client =>
{
    client.Timeout = TimeSpan.FromMinutes(5);
});

// Create client with factory
var factory = serviceProvider.GetRequiredService<IHttpClientFactory>();
var client = new ClickHouseClient("Host=localhost", factory, "ClickHouse");
```

<Note>
  `ClickHouseClient` foi projetado para ter longa vida útil e ser compartilhado em toda a aplicação. Crie-o uma única vez (normalmente como um singleton) e reutilize-o em todas as operações do banco de dados. O cliente gerencia internamente o pool de conexões HTTP.
</Note>

***

<h3 id="executing-queries">
  Executando consultas
</h3>

Use `ExecuteNonQueryAsync` para instruções que não retornam resultados:

```csharp theme={null}
// Criar uma tabela
await client.ExecuteNonQueryAsync(
    "CREATE TABLE IF NOT EXISTS default.my_table (id Int64, name String) ENGINE = Memory"
);

// Remover uma tabela
await client.ExecuteNonQueryAsync("DROP TABLE IF EXISTS default.my_table");
```

Use `ExecuteScalarAsync` para obter um único valor:

```csharp theme={null}
var count = await client.ExecuteScalarAsync("SELECT count() FROM default.my_table");
Console.WriteLine($"Contagem de linhas: {count}");

var version = await client.ExecuteScalarAsync("SELECT version()");
Console.WriteLine($"Versão do servidor: {version}");
```

***

<h3 id="inserting-data">
  Inserção de dados
</h3>

<h4 id="parameterized-inserts">
  Inserções parametrizadas
</h4>

Insira dados por meio de consultas parametrizadas com `ExecuteNonQueryAsync`. Os tipos dos parâmetros devem ser especificados no SQL usando a sintaxe `{name:Type}`:

```csharp theme={null}
using ClickHouse.Driver;
using ClickHouse.Driver.ADO.Parameters;

var parameters = new ClickHouseParameterCollection();
parameters.AddParameter("id", 1L);
parameters.AddParameter("name", "Alice");

await client.ExecuteNonQueryAsync(
    "INSERT INTO default.my_table (id, name) VALUES ({id:Int64}, {name:String})",
    parameters
);
```

***

<h4 id="bulk-insert">
  Inserção em massa
</h4>

Use `InsertBinaryAsync` para inserir grandes volumes de linhas com eficiência. Ele transmite os dados usando o formato binário nativo de linhas do ClickHouse, oferece suporte ao envio paralelo de lotes e evita erros de "URL muito longa" que podem ocorrer com consultas parametrizadas.

```csharp theme={null}
// Preparar dados como IEnumerable<object[]>
var rows = Enumerable.Range(0, 1_000_000)
    .Select(i => new object[] { (long)i, $"value{i}" });

var columns = new[] { "id", "name" };

// Inserção básica
long rowsInserted = await client.InsertBinaryAsync("default.my_table", columns, rows);
Console.WriteLine($"Rows inserted: {rowsInserted}");
```

Para grandes volumes de dados, configure o envio em lotes e o paralelismo com `InsertOptions`:

```csharp theme={null}
var options = new InsertOptions
{
    BatchSize = 100_000,           // Linhas por lote (padrão: 100.000)
    MaxDegreeOfParallelism = 4     // Uploads de lotes em paralelo (padrão: 1)
};
```

<Note>
  * O cliente obtém automaticamente a estrutura da tabela por meio de `SELECT * FROM <table> WHERE 1=0` antes da inserção. Os valores fornecidos devem corresponder aos tipos das colunas de destino. Para ignorar essa consulta, use [`InsertOptions.ColumnTypes` ou `InsertOptions.UseSchemaCache`](#skip-schema-query).
  * Quando `MaxDegreeOfParallelism > 1`, os lotes são enviados em paralelo. As sessões não são compatíveis com inserção em paralelo; desative as sessões ou defina `MaxDegreeOfParallelism = 1`.
  * Use `RowBinaryFormat.RowBinaryWithDefaults` em `InsertOptions.Format` se quiser que o servidor aplique valores DEFAULT às colunas não fornecidas.
</Note>

<h4 id="poco-insert">
  Inserções com POCO
</h4>

Em vez de construir arrays `object[]`, você pode inserir diretamente objetos POCO com tipagem forte. Registre o tipo uma vez e, em seguida, passe `IEnumerable<T>`:

```csharp theme={null}
// Defina um POCO correspondente às colunas da sua tabela
public class SensorReading
{
    public ulong Id { get; set; }
    public string SensorName { get; set; }
    public double Value { get; set; }
    public DateTime Timestamp { get; set; }
}

// Registre o tipo (uma vez por ciclo de vida do cliente)
client.RegisterBinaryInsertType<SensorReading>();

// Insira diretamente — os nomes das colunas são derivados dos nomes das propriedades
var readings = Enumerable.Range(0, 100_000)
    .Select(i => new SensorReading
    {
        Id = (ulong)i,
        SensorName = $"sensor_{i % 10}",
        Value = Random.Shared.NextDouble() * 100,
        Timestamp = DateTime.UtcNow,
    });

long rowsInserted = await client.InsertBinaryAsync("sensors", readings);
```

Por padrão, todas as propriedades públicas legíveis são mapeadas para colunas com base em uma correspondência estrita de nomes que diferencia maiúsculas de minúsculas. Você pode personalizar esse mapeamento com atributos:

```csharp theme={null}
public class Event
{
    [ClickHouseColumn(Name = "event_id")]     // Mapeia para uma coluna com nome diferente
    public ulong Id { get; set; }

    [ClickHouseColumn(Type = "LowCardinality(String)")]  // Tipo explícito do ClickHouse
    public string Category { get; set; }

    public string Payload { get; set; }

    [ClickHouseNotMapped]                     // Exclui do insert
    public string InternalTag { get; set; }
}
```

| Atributo | Finalidade |
| - | - |
| `[ClickHouseColumn(Name = "...")]` | Sobrescrever o nome da coluna de destino |
| `[ClickHouseColumn(Type = "...")]` | Declarar explicitamente o tipo do ClickHouse |
| `[ClickHouseNotMapped]` | Excluir a propriedade da inserção |

Quando **todas** as propriedades mapeadas especificam um `Type` explícito, a consulta de sondagem do esquema é ignorada por completo. Quando apenas algumas propriedades têm tipos explícitos, o driver recorre à consulta de sondagem do esquema para o conjunto completo de colunas.

`InsertBinaryAsync<T>` oferece suporte às mesmas `InsertOptions` (batching, paralelismo, cache de esquema) que a sobrecarga `object[]`.

<Note>
  Diferentemente da sobrecarga `object[]`, `InsertBinaryAsync<T>` não aceita uma lista explícita de colunas. As colunas são determinadas pelas propriedades mapeadas do tipo registrado. Para controlar quais colunas são inseridas, use `[ClickHouseNotMapped]` para excluir propriedades ou `[ClickHouseColumn(Name = "...")]` para renomeá-las.

  Se `ColumnTypes` estiver definido em `InsertOptions`, eles substituirão os atributos do POCO.
</Note>

<h4 id="poco-insert-schema-evolution">
  Evolução do esquema
</h4>

As inserções com POCO funcionam perfeitamente quando colunas são adicionadas à tabela de destino depois que o tipo é registrado. Como o driver insere apenas as colunas mapeadas pelo POCO, quaisquer novas colunas com `DEFAULT` (ou outras expressões padrão) são preenchidas automaticamente pelo servidor. Não é necessário alterar o código nem fazer um novo registro.

<h4 id="insert-query-placement">
  Posicionamento da consulta de inserção
</h4>

Um insert binário escreve sua instrução `INSERT INTO ... FORMAT ...` na primeira linha do corpo da requisição, antes das linhas de dados. O corpo é comprimido por padrão, de modo que mecanismos de roteamento e logging que inspecionam apenas a URL não enxergam a instrução. Defina `InsertOptions.QueryPlacement` como `InsertQueryPlacement.Url` para enviar a instrução no parâmetro de URL `query`, deixando o corpo somente para as linhas:

```csharp theme={null}
var options = new InsertOptions { QueryPlacement = InsertQueryPlacement.Url };
await client.InsertBinaryAsync("events", columns, rows, options);
```

Use essa opção quando um proxy, balanceador de carga ou gateway roteia ou inspeciona o parâmetro `query`, ou quando você quiser a instrução nos logs de acesso e em ferramentas de observabilidade. É opt-in porque, nesse caso, a instrução passa a contar para o comprimento da URL. O limite efetivo é o menor entre os impostos pelo runtime do .NET, por um intermediário e pelo servidor. Do .NET 6 ao .NET 9, `System.Uri` limita a URI de requisição completa e codificada a 65.519 caracteres; o driver lança uma `InvalidOperationException` que o direciona de volta para `InsertQueryPlacement.Body` quando esse limite é excedido. O `http_max_uri_size` do ClickHouse é de 1 MiB por padrão, mas um intermediário pode impor um limite menor. No modo body, a instrução e as linhas não têm esse limite de comprimento de URL; outras opções da requisição ainda podem aparecer na URL.

A configuração é independente de `Compressor`: o corpo é codificado da mesma forma nos dois modos.

***

<h3 id="reading-data">
  Lendo dados
</h3>

Use `ExecuteReaderAsync` para executar consultas SELECT. O `ClickHouseDataReader` retornado fornece acesso tipado às colunas do resultado por meio de métodos como `GetInt64()`, `GetString()` e `GetFieldValue<T>()`.

Chame `Read()` para avançar para a próxima linha. Ele retorna `false` quando não há mais linhas. Acesse as colunas pelo índice (baseado em 0) ou pelo nome da coluna.

```csharp theme={null}
using ClickHouse.Driver.ADO.Parameters;

var parameters = new ClickHouseParameterCollection();
parameters.AddParameter("max_id", 100L);

var reader = await client.ExecuteReaderAsync(
    "SELECT * FROM default.my_table WHERE id < {max_id:Int64}",
    parameters
);

while (reader.Read())
{
    Console.WriteLine($"Id: {reader.GetInt64(0)}, Name: {reader.GetString(1)}");
}
```

<h4 id="poco-read">
  Leitura com POCO
</h4>

Em vez de ler colunas por índice ou nome, você pode direcionar os resultados da consulta diretamente para suas próprias classes. Registre o tipo uma vez no cliente e, em seguida, use `QueryAsync<T>`:

```csharp theme={null}
// Define a POCO matching your result columns
public class SensorReading
{
    public ulong Id { get; set; }
    public DateTime Timestamp { get; set; }

    [ClickHouseColumn(Name = "sensor_name")]
    public string SensorName { get; set; }
    public double Value { get; set; }

}

// Register the type (once per client lifetime)
client.RegisterPocoType<SensorReading>();

// Stream results as typed objects
await foreach (var reading in client.QueryAsync<SensorReading>(
    "SELECT Id, sensor_name, Value, Timestamp FROM sensors"))
{
    Console.WriteLine($"{reading.SensorName}: {reading.Value}");
}
```

<h5 id="poco-read-registration">
  Registro
</h5>

`RegisterPocoType<T>()` configura os mapeamentos de inserção e de leitura e valida ambos de antemão. `RegisterBinaryInsertType<T>()` permanece inalterado e continua sendo exclusivo para inserção por compatibilidade com versões anteriores.

Um tipo registrado deve ter:

* Um construtor público sem parâmetros.
* Pelo menos uma propriedade pública com um setter público que não seja `init`. Propriedades `required` são compatíveis.

<h5 id="poco-read-column-matching">
  Correspondência de colunas
</h5>

A correspondência de colunas diferencia maiúsculas de minúsculas. Colunas de resultado ausentes mantêm as propriedades com seu valor padrão; colunas de resultado adicionais são ignoradas.

O driver não amplia nem reduz valores. Além das representações alternativas listadas abaixo,
o tipo de framework da coluna deve ser atribuível ao tipo da propriedade, e uma incompatibilidade lança
`InvalidOperationException`. Portanto, uma propriedade `object` aceita qualquer coluna.

<h5 id="poco-read-types">
  Tipos de propriedade suportados
</h5>

`QueryAsync<T>` lê cada uma dessas colunas diretamente em uma propriedade correspondente:

| Coluna do ClickHouse | Tipo(s) de propriedade |
| - | - |
| `Int8`/`Int16`/`Int32`/`Int64` | `sbyte`/`short`/`int`/`long` |
| `UInt8`/`UInt16`/`UInt32`/`UInt64` | `byte`/`ushort`/`uint`/`ulong` |
| `Int128`/`UInt128` | `BigInteger` ou os tipos nativos `System.Int128`/`System.UInt128` no .NET 8 e posteriores |
| `Int256`/`UInt256` | `BigInteger` |
| `Float32`/`Float64`/`BFloat16` | `float`/`double`/`float` |
| `Bool` | `bool` |
| `Decimal` | `decimal` ou `ClickHouseDecimal` |
| `Date`/`Date32`/`DateTime`/`DateTime64` | `DateTime`, `DateTimeOffset` ou `DateOnly` |
| `Time`/`Time64` | `TimeSpan` |
| `UUID` | `Guid` |
| `IPv4`/`IPv6` | `IPAddress` |
| `Enum8`/`Enum16` | `string` (o label) ou `int` (o ordinal em wire) |
| `String`/`FixedString` | `string` ou `byte[]` |

Cada linha também aceita a forma anulável do seu tipo de propriedade (`long?`, `DateOnly?` e assim por diante),
independentemente de a coluna ser `Nullable(...)` ou não. Uma propriedade de tipo por valor não anulável em uma coluna
`Nullable(T)` é aceita no registro, mas lança uma exceção quando chega um NULL.

Wrappers como `LowCardinality(T)`, `SimpleAggregateFunction(f, T)` e `Object(T)` são mapeados exatamente como `T`.

Colunas compostas também são suportadas e assumem o tipo do framework indicado na
[referência de tipos de leitura](#clickhouse-native-type-map-reading): `Array(T)` para `T[]`, `Tuple(...)`
para `System.Tuple<...>`, `Nested(...)` para `Tuple<...>[]`, `JSON` para `JsonObject` (ou `string`
sob [`JsonReadMode=String`](#type-map-reading-json)) e `Variant`/`Dynamic` para `object`.

Uma coluna `Map(K, V)` é um caso especial: uma propriedade `List<KeyValuePair<K, V>>` ou `KeyValuePair<K, V>[]`
é lida pelo caminho sem boxing e preserva a ordem em wire e quaisquer chaves repetidas, em qualquer
[`MapReadMode`](#type-map-reading-map). Já uma propriedade `Dictionary<K, V>` funciona apenas no modo padrão.
Os tipos de chave e de valor devem corresponder exatamente, portanto
`Map(String, Nullable(Int32))` requer `KeyValuePair<string, int?>`.

Quando uma coluna oferece mais de um tipo de propriedade (uma coluna `DateTime` como `DateTime`,
`DateTimeOffset` ou `DateOnly`; uma coluna `String` como `string` ou `byte[]`), o tipo de propriedade declarado define a representação. Essas representações alternativas
pertencem ao caminho POCO, portanto estão disponíveis em `QueryAsync<T>`, mas não em `MapTo<T>`.

<h5 id="poco-read-mapto">
  Materializando uma única linha
</h5>

Ao iterar manualmente sobre um leitor, use `ClickHouseDataReader.MapTo<T>()` para materializar a linha atual em um POCO registrado sem avançar o leitor:

```csharp theme={null}
var reader = await client.ExecuteReaderAsync("SELECT Id, SensorName, Value, Timestamp FROM sensors");

while (reader.Read())
{
    SensorReading reading = reader.MapTo<SensorReading>();
    Console.WriteLine($"{reading.SensorName}: {reading.Value}");
}
```

Use `MapTo<T>` quando você mesmo precisar controlar o laço do leitor — por exemplo, para combinar acesso bruto às colunas
com materialização de POCO. Ele lê a linha por meio dos valores boxed do leitor, portanto não oferece
os tipos de propriedade alternativos citados acima, e aloca mais do que `QueryAsync<T>`. Prefira
`QueryAsync<T>` quando você precisar apenas das linhas; consulte
[escolha o caminho de materialização](#perf-read-path) para ver os números.

<h5 id="poco-read-converters">
  Conversores de valores de leitura
</h5>

Um [conversor de valores de leitura](#read-value-conversion) definido no nível do cliente ou por consulta se aplica a ambos os caminhos e
não desativa a leitura sem boxing. O driver converte cada coluna na sobrecarga correspondente à forma como
a coluna foi lida: o `ConvertValue<T>` tipado para uma coluna
sem boxing e o `ConvertValue` com boxing para uma coluna composta. Implemente as duas sobrecargas
de forma consistente; caso contrário, a mesma coluna produzirá resultados diferentes em caminhos diferentes.

<h5 id="poco-read-diagnostics">
  Diagnósticos de registro
</h5>

Quando uma `LoggerFactory` está configurada, `RegisterPocoType<T>()` e `RegisterBinaryInsertType<T>()` geram um log no nível `Debug` (categoria `ClickHouse.Driver.Client`) informando quais propriedades foram mapeadas para quais colunas e quais foram ignoradas, bem como o motivo. Consulte [Logging e diagnósticos](#logging-and-diagnostics).

***

<h3 id="sql-parameters">
  Parâmetros SQL
</h3>

No ClickHouse, o formato padrão para parâmetros em consultas SQL é `{parameter_name:DataType}`.

**Exemplos:**

```sql theme={null}
SELECT {value:Array(UInt16)} as a
```

```sql theme={null}
SELECT * FROM table WHERE val = {tuple_in_tuple:Tuple(UInt8, Tuple(String, UInt8))}
```

```sql theme={null}
INSERT INTO table VALUES ({val1:Int32}, {val2:Array(UInt8)})
```

<Note>
  Os parâmetros SQL de 'bind' são passados como parâmetros de consulta do URI HTTP, portanto o uso excessivo deles pode resultar em uma exceção de "URL too long". Use `InsertBinaryAsync` para inserção de dados em massa e evitar essa limitação.
</Note>

<h4 id="at-style-placeholders">
  Placeholders `@name` no estilo ADO
</h4>

O driver também aceita placeholders `@name`, emitidos por ORMs como o Dapper. Trata-se de uma
conveniência do lado do cliente: antes do envio da requisição, cada um é reescrito como
`{name:ResolvedType}`, de modo que o servidor nunca vê um `@`. Consulte
[resolução de tipos](#parameter-type-mapping) para saber como o tipo é escolhido. Sempre que possível,
use a forma explícita `{name:Type}`.

Um `@name` sem parâmetro correspondente é mantido intacto, para que o servidor o rejeite. A
correspondência diferencia maiúsculas de minúsculas, portanto `@ID` não faz bind de um parâmetro chamado `id`.

<Note>
  Para desativar essa reescrita, defina o switch de AppContext `ClickHouse.Driver.DisableReplacingParameters`
  antes do primeiro uso do driver. Apenas a reescrita do texto é interrompida; os parâmetros continuam
  sendo enviados, de modo que consultas escritas com a sintaxe nativa `{name:Type}` continuam funcionando.
</Note>

<h4 id="identifier-parameters">
  Parâmetros do tipo Identifier
</h4>

O tipo de parâmetro `Identifier` permite vincular com segurança o nome de um banco de dados, tabela ou coluna, em vez de um literal de string entre aspas. Use-o com a sintaxe `{name:Identifier}` em SQL ou definindo `ClickHouseDbParameter.ClickHouseType = "Identifier"`:

```csharp theme={null}
var parameters = new ClickHouseParameterCollection();
parameters.AddParameter("name", "my_database");

await client.ExecuteNonQueryAsync("CREATE DATABASE {name:Identifier}", parameters);
```

```csharp theme={null}
var parameters = new ClickHouseParameterCollection();
parameters.AddParameter("col", "user_id");

var reader = await client.ExecuteReaderAsync("SELECT {col:Identifier} FROM t", parameters);
```

O valor é enviado literalmente, e o servidor o substitui como um identificador SQL não entre aspas, aplicando seu próprio uso de backticks e escape. Identificadores que contêm caracteres especiais (inclusive backticks) podem fazer o percurso de ida e volta com segurança.

***

<h3 id="query-id">
  ID da consulta
</h3>

Cada consulta recebe um `query_id` único, que pode ser usado para obter dados da tabela `system.query_log` ou cancelar consultas de longa execução. Você pode especificar um ID de consulta personalizado por meio de `QueryOptions`:

```csharp theme={null}
var options = new QueryOptions
{
    QueryId = $"report-{Guid.NewGuid()}"
};

var reader = await client.ExecuteReaderAsync(
    "SELECT * FROM large_table",
    parameters: null,
    options: options
);
```

<Tip>
  Se você estiver especificando um `QueryId` personalizado, garanta que ele seja único em cada chamada. Um GUID aleatório é uma boa opção.
</Tip>

***

<h3 id="parameter-type-mapping">
  Mapeamento personalizado de tipos de parâmetro
</h3>

Ao usar parâmetros no estilo `@` (por exemplo, `WHERE id = @id`), o driver infere automaticamente o tipo do ClickHouse com base no tipo de valor do .NET. Por exemplo, `int` é mapeado para `Int32`.

<Warning>
  **Comportamento de parâmetros `DateTime` inferidos**

  Para parâmetros no estilo `@` sem hint `{name:Type}` no SQL e sem `ClickHouseType` definido, valores que representam um instante são inferidos como `DateTime('UTC')` em vez de um `DateTime` simples. `DateTime` com `Kind` igual a `Utc` ou `Local`, e todos os valores `DateTimeOffset`, são enviados como `DateTime('UTC')`, preservando o instante em qualquer fuso horário do servidor.

  Hints explícitos (`{name:DateTime}`) têm precedência sobre a inferência e são a forma recomendada de criar consultas.
</Warning>

Para substituir esses padrões, defina `ParameterTypeResolver` em `ClickHouseClientSettings`. Isso é útil quando você quer que todos os parâmetros `DateTime` usem `DateTime64(3)` para precisão de milissegundos ou que todos os decimais usem uma escala específica, sem precisar definir `ClickHouseType` em cada parâmetro individualmente.

**Usando `DictionaryParameterTypeResolver` para mapeamentos simples de tipo:**

```csharp theme={null}
using ClickHouse.Driver.ADO.Parameters;

var settings = new ClickHouseClientSettings("Host=localhost")
{
    ParameterTypeResolver = new DictionaryParameterTypeResolver(new Dictionary<Type, string>
    {
        [typeof(DateTime)] = "DateTime64(3)",
        [typeof(decimal)] = "Decimal64(4)",
    }),
};
using var client = new ClickHouseClient(settings);

var parameters = new ClickHouseParameterCollection();
parameters.AddParameter("dt", DateTime.UtcNow);     // Mapped to DateTime64(3)
parameters.AddParameter("amount", 99.1234m);         // Mapped to Decimal64(4)

await client.ExecuteReaderAsync("SELECT @dt, @amount", parameters);
```

**`IParameterTypeResolver` personalizado para cenários avançados:**

Para resolução com base no valor ou no nome, implemente diretamente a interface `IParameterTypeResolver`. Retorne `null` para usar a inferência padrão:

```csharp theme={null}
public class SmartDecimalResolver : IParameterTypeResolver
{
    public string ResolveType(Type clrType, object value, string parameterName)
    {
        if (clrType != typeof(decimal))
            return null; // Fall through to default

        var scale = (decimal.GetBits((decimal)value)[3] >> 16) & 0x7F;
        return scale <= 4 ? $"Decimal64({scale})" : $"Decimal128({scale})";
    }
}
```

Você também pode definir um resolver para uma única consulta por meio de `QueryOptions.ParameterTypeResolver`. Quando definido, ele tem precedência sobre o resolver no nível do cliente.

**Precedência da resolução de tipos:**

O resolver é uma etapa em uma cadeia de precedência. Da maior para a menor prioridade:

1. `ClickHouseType` explícito definido no parâmetro
2. Type hint de SQL da sintaxe `{name:Type}` na consulta
3. `IParameterTypeResolver` (de `QueryOptions.ParameterTypeResolver`, com fallback para `ClickHouseClientSettings.ParameterTypeResolver`)
4. Inferência de tipo integrada (`TypeConverter.ToClickHouseType`)

O resolver também funciona com o caminho do ADO.NET `ClickHouseConnection` — as configurações são herdadas pelas conexões criadas a partir do cliente.

***

<h3 id="parameter-value-formatting">
  Formatação personalizada de valores de parâmetros
</h3>

`IParameterFormatter` é um hook que define como os valores dos parâmetros são serializados. Use-o quando a formatação padrão (por exemplo, precisão de DateTime, convenção decimal, escaping de strings, representação de números) não corresponder ao que seu esquema ou suas ferramentas downstream esperam.

Defina `ParameterFormatter` em `ClickHouseClientSettings` para instalar um formatador para todas as consultas parametrizadas. O formatador recebe o valor, o nome do tipo ClickHouse resolvido e o nome do parâmetro, e retorna a representação em string que é enviada ao servidor. Retorne `null` para deixar o processamento seguir para o formatador padrão.

**Usando `DictionaryParameterFormatter` para formatação simples por tipo CLR:**

```csharp theme={null}
using ClickHouse.Driver.ADO.Parameters;

var settings = new ClickHouseClientSettings("Host=localhost")
{
    ParameterFormatter = new DictionaryParameterFormatter(new Dictionary<Type, Func<object, string>>
    {
        [typeof(DateTime)] = v => ((DateTime)v).ToString("yyyy-MM-ddTHH:mm:ss.ffffff",
            System.Globalization.CultureInfo.InvariantCulture),
        [typeof(decimal)] = v => ((decimal)v).ToString("F4",
            System.Globalization.CultureInfo.InvariantCulture),
    }),
};
using var client = new ClickHouseClient(settings);
```

**`IParameterFormatter` personalizado para casos avançados:**

```csharp theme={null}
public class FixedDecimalFormatter : IParameterFormatter
{
    public string Format(object value, string typeName, string parameterName)
    {
        if (value is decimal d)
            return d.ToString("F4", System.Globalization.CultureInfo.InvariantCulture);
        return null; // Fall through for anything else
    }
}
```

Você também pode definir um formatador por consulta via `QueryOptions.ParameterFormatter`. Quando definido, ele tem precedência sobre o formatador em nível de cliente.

**Valores compostos:**

O formatador é executado tanto para parâmetros de collection de nível superior quanto para cada elemento dentro de valores compostos (`Array`, `Tuple`, `Map`, `Nullable`, `LowCardinality`, `Variant`). Por exemplo, um mapeamento `typeof(int)` formata individualmente cada elemento `Int32` de um `Array(Int32)`.

**Uso de aspas simples em contextos compostos:**

Para types do ClickHouse semelhantes a string (`String`, `FixedString`, `Enum8`, `Enum16`, `IPv4`, `IPv6`, `UUID`) embutidos em um literal composto, o driver envolve a saída do formatador em aspas simples, mas não escapa seu conteúdo. Se a string retornada contiver uma aspa simples ou barra invertida sem escape, o literal composto ficará malformado e o servidor rejeitará a consulta.

Parâmetros de string de nível superior (não embutidos em um composto) são usados literalmente, sem aspas, portanto não é necessário escaping nesse caso.

**Precedência do formatador:**

1. `IParameterFormatter` (de `QueryOptions.ParameterFormatter`, com fallback para `ClickHouseClientSettings.ParameterFormatter`). Se ele retornar um valor não nulo, esse valor será usado.
2. Formatação interna específica de cada tipo em `HttpParameterFormatter`.

O formatador não é consultado para valores `null` ou `DBNull`; eles são sempre serializados como a sentinela nula do ClickHouse (`\N`).

***

<h3 id="read-value-conversion">
  Conversão personalizada de valores lidos
</h3>

`IReadValueConverter` permite transformar os valores retornados pelo leitor de dados após a desserialização, sem alterar o tipo CLR deles. Usos típicos: definir `DateTime.Kind = Utc` em uma coluna `DateTime` sem timezone, aparar ou normalizar strings, ou fazer o pós-processamento de uma coluna JSON antes que ela chegue ao código da aplicação.

Defina `ReadValueConverter` em `ClickHouseClientSettings` para instalar um conversor para todas as leituras. O conversor é invocado uma vez por coluna por linha, tanto no caminho com boxing (`GetValue`) quanto no genérico (`GetFieldValue<T>`). Quando nenhum conversor é definido, a sobrecarga é zero — o leitor retorna os valores diretamente.

**Usando `DictionaryReadValueConverter` para conversão simples por tipo CLR:**

```csharp theme={null}
using ClickHouse.Driver.ADO.Readers;

var converter = new DictionaryReadValueConverter()
    .For<DateTime>(dt => DateTime.SpecifyKind(dt, DateTimeKind.Utc))
    .For<string>(s => s.Trim());

var settings = new ClickHouseClientSettings("Host=localhost")
{
    ReadValueConverter = converter,
};
using var client = new ClickHouseClient(settings);
```

Valores cujo tipo CLR em runtime não está registrado com `For<T>` passam inalterados. O despacho é feito pelo tipo CLR exato, portanto registre o tipo real produzido pelo leitor (por exemplo, `For<JsonObject>` para uma coluna JSON em `JsonReadMode.Binary`).

**`IReadValueConverter` personalizado para cenários avançados:**

Se você precisar despachar com base na string de tipo do lado do ClickHouse (por exemplo, para distinguir `DateTime` de `DateTime('UTC')` — ambos aparecem como o mesmo tipo CLR), implemente `IReadValueConverter` diretamente:

```csharp theme={null}
public class UtcKindForNoTzDateTimeConverter : IReadValueConverter
{
    public object ConvertValue(object value, string columnName, string clickHouseType)
    {
        if (value is DateTime dt && clickHouseType == "DateTime")
            return DateTime.SpecifyKind(dt, DateTimeKind.Utc);
        return value;
    }

    public T ConvertValue<T>(T value, string columnName, string clickHouseType)
    {
        if (typeof(T) == typeof(DateTime) && value is DateTime dt && clickHouseType == "DateTime")
            return (T)(object)DateTime.SpecifyKind(dt, DateTimeKind.Utc);
        return value;
    }
}
```

O conversor deve preservar o tipo CLR de runtime; os metadados da coluna (`GetFieldType`, `GetSchemaTable`) não passam por ele e devem permanecer consistentes com o valor retornado.

Você também pode definir um conversor por consulta via `QueryOptions.ReadValueConverter`; quando definido, ele tem precedência sobre o conversor no nível do cliente.

**Limite do despacho:**

O conversor é invocado uma vez por coluna com o valor completo da célula desserializada; ele **não** processa recursivamente contêineres compostos. Para uma coluna `Array(Int32)`, o valor passado é um `int[]`; para `Tuple(Int32, String)`, é um `ITuple`.

**Qual sobrecarga é executada:**

Ambas as sobrecargas devem ser consistentes entre si, porque a que o driver chama depende de como o chamador leu a
coluna:

* `ConvertValue<T>` — os acessadores tipados `GetByte`, `GetSByte`, `GetInt16`/`32`/`64`,
  `GetUInt16`/`32`/`64`, `GetFloat`, `GetDouble`, `GetGuid`, `GetDateTime`, `GetIPAddress`,
  `GetBigInteger` e `GetFieldValue<T>`, além de todas as colunas sem boxing no
  [caminho de leitura POCO](#poco-read-converters).
* `ConvertValue` (com boxing) — `GetValue`, `GetValues`, os indexadores, `GetChar`, `GetTuple` e os
  caminhos de coerção em `GetBoolean`, `GetDecimal` e `GetString`.

`IsDBNull` não executa nenhum conversor: ele lê o indicador de nulo diretamente, portanto um conversor nunca pode
alterar se um valor conta como nulo. `TryGetEnumOrdinal` também o ignora — veja
[lendo o ordinal de um enum](#ado-net-reader-enum-ordinal).

O conversor funciona com o caminho `ClickHouseConnection` do ADO.NET — as configurações são herdadas pelas conexões criadas a partir do cliente.

***

<h3 id="raw-streaming">
  Fluxo bruto
</h3>

Use `ExecuteRawResultAsync` para transmitir diretamente os resultados da `consulta` em um formato específico, sem passar pelo leitor de dados. Isso é útil para exportar dados para arquivos ou repassá-los a outros sistemas:

```csharp theme={null}
using var result = await client.ExecuteRawResultAsync(
    "SELECT * FROM default.my_table LIMIT 100 FORMAT JSONEachRow"
);

await using var stream = await result.ReadAsStreamAsync();
using var reader = new StreamReader(stream);
var json = await reader.ReadToEndAsync();
```

Formatos comuns: `JSONEachRow`, `CSV`, `TSV`, `Parquet`, `Native`. Consulte a [documentação sobre formatos](/pt-BR/reference/formats/index) para ver todas as opções.

***

<h3 id="per-query-accept-encoding">
  Compressão de transporte por consulta
</h3>

Por padrão, o cliente negocia `zstd, lz4, gzip, deflate` quando `Compression=true` (o padrão da string de conexão) e decodifica o fluxo por conta própria, de forma transparente.

Para exportações brutas (por exemplo, Parquet, Arrow, Native), talvez você queira negociar um codec diferente (por exemplo, `zstd` ou `lz4`) para trocar CPU por largura de banda sem alterar a configuração da conexão como um todo. `QueryOptions.AcceptEncoding` e `ClickHouseCommand.AcceptEncoding` definem o cabeçalho HTTP `Accept-Encoding` para uma única solicitação, substituindo qualquer valor padrão definido anteriormente, e forçam `enable_http_compression=1` na URL (o que o ClickHouse exige antes de respeitar `Accept-Encoding`).

```csharp theme={null}
using var result = await client.ExecuteRawResultAsync(
    "SELECT * FROM events FORMAT Parquet",
    options: new QueryOptions { AcceptEncoding = "zstd" });

// Decode yourself or write to a file
await using var body = await result.ReadAsStreamAsync();
```

<h4 id="per-query-accept-encoding-httpclient">
  Configuração do HttpClient
</h4>

Não há nada a configurar: o `HttpClient` construído pelo driver mantém `AutomaticDecompression` em `DecompressionMethods.None` e o próprio driver decodifica as respostas, de modo que o `Content-Encoding` nunca é removido sem o seu conhecimento e o corpo bruto chega até você exatamente como o servidor o enviou.

<Warning>
  Se você fornecer seu próprio `HttpClient`, mantenha `AutomaticDecompression` desativado também. Não se trata apenas de uma configuração do lado da resposta: no momento do envio, o handler **adiciona todos os algoritmos de sua máscara que estiverem ausentes no `Accept-Encoding` de saída**. Um handler com `GZip | Deflate`, portanto, transforma um `AcceptEncoding = "lz4"` explícito em `lz4, gzip, deflate` e um `"identity"` explícito em `identity, gzip, deflate` on the wire — e, como o ClickHouse resolve o cabeçalho pela sua própria preferência fixa de codec (ignorando a ordem e os valores q), ele pode responder com um codec que você nunca solicitou, que o handler então decodifica e remove, de modo que você nem chega a perceber que isso aconteceu. Manter a máscara desativada garante que a oferta seja exatamente a que você escolheu.
</Warning>

<Warning>
  Se `AcceptEncoding` solicitar um codec que o driver não consegue decodificar (`snappy`), apenas `ExecuteRawResultAsync` é seguro. `ExecuteReaderAsync`, `ExecuteScalarAsync` e `ExecuteNonQueryAsync` falham com uma `NotSupportedException` que nomeia o codec (anteriormente, eles interpretavam os bytes comprimidos como o format do resultado e produziam lixo).
</Warning>

<h4 id="per-query-accept-encoding-errors">
  Corpos de erro
</h4>

Quando o servidor responde com um 4xx/5xx e `enable_http_compression=1` foi definido, ele compacta o corpo do erro com o mesmo codec que usaria em uma resposta bem-sucedida. O driver decodifica esses corpos para todos os codecs que suporta (`lz4`, `zstd`, `gzip`, `deflate`, `br`/`brotli`), para que a mensagem em `ClickHouseServerException` seja legível. Para qualquer outro caso (`snappy`, …), ele retorna uma mensagem substituta que informa o codec e aponta para `system.query_log`, onde está o texto original do erro.

***

<h3 id="response-decompression">
  Descompressão da resposta
</h3>

`Accept-Encoding` apenas pede ao servidor que comprima a resposta — algo ainda precisa decodificá-la. O próprio driver faz isso, com base no `Content-Encoding` da resposta, de modo que todas as APIs normais de leitura (`ExecuteReaderAsync`, `ExecuteScalarAsync`, `ExecuteNonQueryAsync`, `QueryAsync<T>`, Dapper, EF Core, linq2db) funcionam com uma resposta comprimida sem nenhuma configuração adicional. Ele decodifica `lz4`, `zstd`, `gzip`, `deflate` e `br`; `snappy` não é suportado.

Por padrão, o driver anuncia **`zstd, lz4, gzip, deflate`**, e o ClickHouse responde com `zstd`. Para escolher outra opção, defina o `Accept-Encoding` manualmente — para todo o cliente:

```csharp theme={null}
using var client = new ClickHouseClient(new ClickHouseClientSettings("Host=localhost")
{
    AcceptEncoding = "br",      // decodable, but not advertised by default
});
```

por consulta, o que tem precedência:

```csharp theme={null}
using var reader = await client.ExecuteReaderAsync(
    "SELECT * FROM events",
    options: new QueryOptions { AcceptEncoding = "identity" });   // opt this query out
```

ou na connection string, para usuários de ORM que nunca mexem em `ClickHouseClientSettings`:

```text theme={null}
Host=localhost;AcceptEncoding=br, gzip
```

Defini-lo também força `enable_http_compression=1` na URL, o que o ClickHouse exige antes de sequer respeitar o cabeçalho — inclusive quando `UseCompression` é `false`, já que nomear um codec explicitamente é interpretado como um pedido de compressão. Sem nenhum valor definido, `UseCompression=false` não envia nenhum `Accept-Encoding`.

`Accept-Encoding` pode ser definido em quatro lugares. Prevalece o primeiro deles que nomear um codec:

1. `QueryOptions.AcceptEncoding` (ou `ClickHouseCommand.AcceptEncoding`)
2. `CustomHeaders["Accept-Encoding"]` na consulta
3. `CustomHeaders["Accept-Encoding"]` no cliente
4. `ClickHouseClientSettings.AcceptEncoding`, ou a palavra-chave de string de conexão `AcceptEncoding`

Se nenhum deles nomear um codec, o driver envia sua lista padrão. Um valor que não nomeia nenhum codec (null, vazio,
espaço em branco ou apenas vírgulas) é considerado não definido e passa para o próximo lugar. Para desativar a compressão, use `identity`.

**Quem escolhe o codec é o servidor, não o cliente.** O ClickHouse examina o `Accept-Encoding` em busca de tokens seguindo sua própria ordem fixa de preferência — `zstd` > `br` > `lz4` > `snappy` > `gzip` > `deflate` — e ignora tanto a ordem em que você os lista quanto quaisquer q-values. Portanto, o cabeçalho é um anúncio de capacidades, não uma exigência, e a única forma de influenciar a escolha é decidir quais tokens deixar de fora. O padrão inclui `zstd`, então uma consulta padrão é respondida com zstd; os tokens restantes funcionam como fallback. `br` é decodificável, mas não é anunciado por padrão.

A comparação entre os codecs em tamanho de payload, CPU do servidor e CPU do cliente depende dos seus dados, do seu link e do `http_zlib_compression_level` do servidor (padrão de fábrica: 3) — veja [Ajuste da compressão](#tuning-compression).

* **`http_zlib_compression_level`.** Essa configuração se aplica a todos os codecs HTTP, e o valor padrão é 3. Esse valor deve ser ajustado conforme seus dados, a velocidade do link e o uso de CPU.
* **Um cliente CPU-bound em um link rápido.** O driver decodifica o corpo da resposta na thread chamadora, portanto, quando a rede não é o gargalo, a velocidade de decodificação no lado do cliente pode se tornar o fator limitante.

Solicite um codec diferente por consulta, ou para todo o cliente, sempre que um desses casos se aplicar:

```csharp theme={null}
using var reader = await client.ExecuteReaderAsync(
    "SELECT * FROM events",
    options: new QueryOptions { AcceptEncoding = "lz4" });   // decode this one with lz4 instead
```

Como a decisão é tomada com base na resposta, o corpo é decodificado sempre que seu `Content-Encoding` assim indicar, independentemente do que foi solicitado: se estiver ausente ou for `identity`, o conteúdo passa intacto; se for um codec suportado, é decodificado; e qualquer outro valor gera um erro que o identifica. Não há risco de decodificação dupla — se o `AutomaticDecompression` de um handler fornecido pelo chamador já tiver decodificado o corpo, ele também remove o `Content-Encoding`, de modo que o driver vê o conteúdo em texto simples e não o altera.

**Resultados brutos não anunciam nenhum codec.** `ExecuteRawResultAsync` (e os públicos `PostStreamAsync` / `InsertRawStreamAsync`) entregam o corpo a você tal como veio, portanto, a menos que você mesmo indique um codec, eles não solicitam nenhum — nada no driver decodifica um corpo desse tipo, de modo que oferecer um codec ali transformaria silenciosamente uma exportação em um arquivo comprimido. A regra, portanto, é simples e independe de como o `HttpClient` esteja configurado: **um corpo sem processamento chega exatamente como o servidor o enviou, e o servidor envia texto simples a menos que você peça um codec.** Pedir um (para todo o client ou por consulta) é a forma de exportar bytes comprimidos de propósito.

Um `AcceptEncoding` explícito (em qualquer um dos níveis) continua valendo para requisições brutas, e `ClickHouseRawResult.ReadDecompressedStreamAsync()` decodifica o resultado quando você quiser isso; `ReadAsStreamAsync`, `ReadAsByteArrayAsync`, `ReadAsStringAsync` e `CopyToAsync` sempre retornam os bytes exatamente como chegaram.

```csharp theme={null}
using var result = await client.ExecuteRawResultAsync(
    "SELECT * FROM events FORMAT JSONEachRow",
    options: new QueryOptions { AcceptEncoding = "lz4" });

Console.WriteLine(result.ContentEncoding); // "lz4"

await using var body = await result.ReadDecompressedStreamAsync();
using var bodyReader = new StreamReader(body);
var json = await bodyReader.ReadToEndAsync();
```

Leia o stream retornado até o fim antes que ele saia de escopo, como acima. Quando a resposta *está* comprimida, você recebe um decoder criado com `leaveOpen`, de modo que descartá-lo mantém a resposta intacta; quando ela **não** está comprimida, você recebe o próprio stream de conteúdo HTTP, e descartá-lo encerra o corpo. Em qualquer um dos casos, o `ClickHouseRawResult` é o dono da resposta — não chame seus outros membros de leitura depois que o stream tiver sido descartado. Descartar o `ClickHouseRawResult` é sempre obrigatório e, por si só, suficiente: isso libera tanto a resposta quanto qualquer decoder inserido aqui (decoders mantêm buffers do pool). Portanto, o `await using` acima é opcional, mas é seguro mantê-lo. Chamadas sequenciais repetidas devolvem o mesmo stream; o tipo não é seguro para uso concorrente.

Veja [Select\_007\_ResponseCompression.cs](https://github.com/ClickHouse/clickhouse-cs/blob/main/examples/Select/Select_007_ResponseCompression.cs) para um exemplo executável.

<h4 id="insert-compression">
  Compressão de insert (requisição)
</h4>

Zstd é o codec padrão para inserts: `InsertOptions.Compressor` tem como valor inicial `ZstdCompressor.Default`,
que corresponde ao zstd no nível 3. Defina outro compressor para alterar o codec, ou `null` para enviar o
corpo sem compressão.

```csharp theme={null}
var options = new InsertOptions { Compressor = GZipCompressor.Default };  // Content-Encoding: gzip
await client.InsertBinaryAsync("events", columns, rows, options);
```

O driver inclui quatro codecs. Cada um possui uma instância `Default` e um construtor que recebe um nível
e o tamanho do write buffer:

| Compressor | `Content-Encoding` | Construtor | `Default` |
| - | - | - | - |
| `ZstdCompressor` | `zstd` | `(int level = 3, int bufferSize = 262144)` | nível 3 |
| `Lz4Compressor` | `lz4` | `(Lz4Level level = Lz4Level.Fast, int bufferSize = 262144)` | `Lz4Level.Fast` |
| `GZipCompressor` | `gzip` | `(CompressionLevel level = CompressionLevel.Fastest, int bufferSize = 262144)` | `Fastest` |
| `BrotliCompressor` | `br` | `(CompressionLevel level = CompressionLevel.Fastest, int bufferSize = 262144)` | `Fastest` |

```csharp theme={null}
var options = new InsertOptions { Compressor = new ZstdCompressor(level: 1) };
```

<Note>
  *Compartilhe instâncias de compressor.* Cada `Default` é uma única instância compartilhada, e os quatro compressores
  podem ser usados com segurança por várias threads ao mesmo tempo — que é justamente o que acontece quando
  `InsertOptions.MaxDegreeOfParallelism` é maior que 1, já que cada insert usa um compressor por
  batch. Nenhum deles implementa `IDisposable`. Crie sua própria instância uma única vez e reutilize-a, da
  mesma forma que `Default` é usado.
</Note>

<h5 id="custom-compressor">
  Um codec personalizado
</h5>

`IClickHouseCompressor` é público, e uma implementação precisa fornecer apenas dois membros:

```csharp theme={null}
public sealed class MyCompressor : IClickHouseCompressor
{
    public string ContentEncoding => "my-codec";

    public Stream Compress(Stream destination, bool leaveOpen) => /* a compressing write stream */;
}
```

O servidor deve aceitar o `Content-Encoding` que você indicar. Os demais membros —
`Decompress`, `MethodByte`, `MaxEncodedLength`, `Encode` e `Decode` — têm implementações
padrão que lançam `NotSupportedException`, portanto sobrescreva apenas os que seu codec precisar.
Implemente `Decompress` para decodificar corpos de resposta além de comprimir requisições, e lance
`InvalidDataException` a partir do stream que ele retorna quando um corpo estiver corrompido ou em formato incorreto.

`InsertOptions.Compressor` rege apenas o insert binário. Os demais corpos de requisição do driver são comprimidos por regras diferentes, e nenhum deles passa por ele:

* **Toda requisição de texto SQL** (`ExecuteReaderAsync`, `ExecuteScalarAsync`, `ExecuteNonQueryAsync`, `QueryAsync<T>`, `ExecuteRawResultAsync`, a camada ADO.NET) envia sua instrução com `Content-Encoding: gzip` sempre que `UseCompression` for `true` — ou seja, por padrão. O codec não é configurável: `AcceptEncoding` controla apenas a resposta, então a escolha é gzip ou nada. Com `Compression=false`, a instrução é enviada sem compressão. As instruções são pequenas, então isso raramente merece atenção — mas é bom saber quando você estiver observando requisições em um proxy ou em uma captura de pacotes.
* **Um corpo multipart** — uma consulta cujos parâmetros são enviados como form data (`UseFormDataParameters=true`) — é sempre enviado sem compressão, independentemente do que diga `UseCompression`.
* **Um upload bruto** (`InsertRawStreamAsync`, `PostStreamAsync`) usa sua própria flag por chamada e não consulta nem `UseCompression` nem `InsertOptions.Compressor`: gzip quando a flag está definida, sem compressão caso contrário. Observe que o parâmetro `useCompression` de `InsertRawStreamAsync` tem valor padrão `true`, então um upload bruto é comprimido com gzip a menos que você passe `false` — mesmo com `Compression=false` no client.

***

<h3 id="tuning-compression">
  Ajustando a compressão
</h3>

A compressão troca CPU por bytes. Se essa troca compensa depende quase inteiramente da velocidade
do seu link em relação à velocidade de execução do codec. Não existe uma configuração adequada para
todos.

<h4 id="the-one-number-that-decides-it">
  O único número que decide
</h4>

Comprimir vale a pena desde que o codec seja mais rápido que a rede.

Esse limite é mais baixo do que a maioria das pessoas imagina no caminho de leitura, porque o ClickHouse comprime
as respostas HTTP em thread única no buffer de saída. Medido em um service do ClickHouse Cloud com 16 vCPUs
(`hits`, RowBinary, nível 3), o servidor produz saída comprimida a aproximadamente 100-200MB/s.

Portanto, para um resultado grande, e supondo que apenas uma consulta seja processada por vez, a compressão deixa de compensar por volta de 100 MB/s. Um único stream HTTPS
dentro de uma mesma região de nuvem costuma superar esse valor, enquanto qualquer tráfego que atravesse a internet pública, uma VPN ou a fronteira entre regiões normalmente fica abaixo dele.

O caminho de insert tolera compressão em links mais rápidos, porque o client comprime em um core próprio e costuma ser mais rápido que a compressão de resposta do servidor.

<h4 id="rough-guide-by-deployment">
  Guia aproximado por tipo de implantação
</h4>

| Onde seu cliente executa | Largura de banda típica | Leituras | Inserts |
| - | - | - | - |
| Mesmo host / loopback | > 500 MB/s | `identity` | `lz4` mais rápido, ou nenhum |
| Mesma região, mesma nuvem | \~100–500 MB/s | `identity` ou `lz4` | `zstd:1` |
| Entre regiões, mesma nuvem | \~10–100 MB/s | `zstd` | `zstd:3` |
| Internet / VPN / nuvem diferente | \< 25 MB/s | `zstd` | `zstd:3` |
| Tarifado ou muito restrito | \< 5 MB/s | `zstd` | `zstd:5`+ ou `br` |

Três aspectos que esta tabela não contempla:

* **Custo de egress:** se você é cobrado pela transferência de dados, os bytes têm um preço que vai além da latência, e isso
  favorece uma compressão mais alta independentemente da velocidade do link.
* **Resultados pequenos:** tudo o que foi dito acima vale para payloads grandes. Em respostas pequenas, o codec quase não
  importa e a sobrecarga por requisição é o que predomina.
* **Inserts em paralelo elevam os limiares de insert.** Todos os números de throughput acima se referem a uma *única* thread. `InsertOptions.MaxDegreeOfParallelism` tem `1` como valor padrão, mas aumentá-lo faz com que os batches sejam comprimidos de forma concorrente, de modo que a taxa agregada de codificação do cliente escala aproximadamente com os núcleos que você disponibilizar. Ou seja, em um link rápido, ainda pode valer a pena comprimir um insert paralelo
  bem depois do ponto em que um insert de thread única deixa de compensar. Trate as linhas de insert da tabela como um *piso* e, se você já faz batches em paralelo, refaça os testes antes de concluir que seu link é rápido demais para compressão.

O caminho de leitura só é paralelizado entre múltiplas consultas.

<h4 id="choosing-a-codec">
  Escolhendo um codec
</h4>

| Codec | Razão | Use quando | Fique atento a |
| - | - | - | - |
| `lz4` | mais baixa | Links rápidos; a CPU é mais escassa que a largura de banda. De longe o mais barato para decodificar e o mais rápido em resultados pequenos — o que o torna o codec a indicar quando você quer abandonar o padrão zstd. | Ele **não tem codificador de entropia**, portanto, em dados enviesados mas não repetitivos (longas sequências de texto numérico, por exemplo), sua razão fica bem atrás de todos os outros. É também o codec mais penalizado pelo aumento de `http_zlib_compression_level`: passar do nível 1 para o 3 custa \~2,7× mais CPU para \~29% menos bytes. |
| `zstd` | alta | A escolha de propósito geral sempre que houver uma rede real envolvida. Melhor razão por CPU na faixa que importa e, no nível 3, supera o `lz4` em bytes, *em* CPU do servidor *e* em tempo de relógio. | é mais caro que o `lz4` para **decodificar** — 1,6× no nível 3 em nossas medições, embora no nível 1 os dois sejam comparáveis — e o driver decodifica na thread que fez a chamada. Especificamente com `http_zlib_compression_level=1`, consome um pouco *mais* CPU do servidor que o `lz4`. |
| `gzip` | média | Interoperabilidade — universalmente compreendido por proxies e gateways. | É superado em todos os aspectos tanto pelo `lz4` quanto pelo `zstd` em nossas medições: maior que o `zstd` e, ainda assim, custa várias vezes mais CPU para codificar e de 5 a 9× mais para decodificar. Escolha-o por compatibilidade, não por desempenho. |
| `br` | mais alta em níveis baixos | A largura de banda é realmente a restrição limitante e você pode gastar CPU por isso. | Despenca feio em níveis mais altos — com `http_zlib_compression_level=6`, medimos de 3 a 4× a CPU de servidor do `zstd`. Não é anunciado por padrão, porque supera todo token de fallback da lista padrão. |

<h4 id="levels">
  Níveis
</h4>

A compressão da resposta é controlada por uma única configuração de servidor, `http_zlib_compression_level`, que se aplica a *todos* os codecs HTTP, não apenas ao zlib. O padrão é 3.

Não mexa nela a menos que tenha medições que justifiquem. Acima do padrão, ganha-se muito pouco em tamanho ao custo de muita CPU (para `zstd`, 3 → 6 praticamente dobra a CPU do servidor em troca de \~14% menos bytes), e o `br` se torna patológico. Abaixo dele, no nível 1, o cenário muda de verdade: o `lz4` fica muito mais barato e o `zstd` perde sua vantagem de CPU sobre ele. Defina o valor por consulta, se necessário:

```csharp theme={null}
using var reader = await client.ExecuteReaderAsync(
    "SELECT * FROM events",
    options: new QueryOptions
    {
        AcceptEncoding = "zstd",
        CustomSettings = new Dictionary<string, object> { ["http_zlib_compression_level"] = 1 },
    });
```

<h4 id="measuring-your-own-crossover">
  Medindo seu próprio ponto de cruzamento
</h4>

A maneira mais rápida de otimizar a escolha do codec e do nível de compressão é medir o tempo da mesma consulta com alguns codecs e comparar os resultados.

```csharp theme={null}
foreach (var codec in new[] { "identity", "lz4", "zstd" })
{
    var sw = Stopwatch.StartNew();
    using var reader = await client.ExecuteReaderAsync(
        "SELECT ... FROM big_table",
        options: new QueryOptions { AcceptEncoding = codec });
    while (await reader.ReadAsync()) { }
    Console.WriteLine($"{codec,-9} {sw.ElapsedMilliseconds} ms");
}
```

Para ver o lado do servidor desse mesmo cenário, leia os `ProfileEvents` a partir de `system.query_log` — defina
`QueryOptions.QueryId` para conseguir localizar a linha:

```sql theme={null}
SELECT ProfileEvents['UserTimeMicroseconds'] + ProfileEvents['SystemTimeMicroseconds'] AS cpu_us,
       ProfileEvents['NetworkSendBytes'] AS sent_bytes,
       query_duration_ms
FROM system.query_log
WHERE query_id = 'your-query-id' AND type = 'QueryFinish';
```

Uma armadilha caso você mesmo faça esse benchmark: um `LIMIT n` isolado, sem `ORDER BY`, retorna *linhas diferentes
a cada execução*, de modo que cada repetição comprime dados diferentes e as razões viram ruído. Compare
sempre contra um result set fixo.

***

<h3 id="raw-stream-insert">
  Inserção via raw stream
</h3>

Use `InsertRawStreamAsync` para inserir dados diretamente de arquivos ou de streams em memória em formatos como CSV, JSON, Parquet ou qualquer [formato suportado pelo ClickHouse](/pt-BR/reference/formats/index).

**Inserir a partir de um arquivo CSV:**

```csharp theme={null}
using var response = await client.InsertRawStreamAsync(
    table: "my_table",
    stream: File.OpenRead("data.csv"),
    format: "CSV",
    columns: ["id", "product", "price"] // Optional: specify columns
);
```

<Warning>
  *O driver assume a propriedade do stream.* `InsertRawStreamAsync` e `PostStreamAsync` descartam o
  stream que você fornece assim que a requisição termina, tenha ela sido bem-sucedida ou não. Não o descarte
  você mesmo e não o reutilize depois — por isso o exemplo acima não envolve o
  `FileStream` em um `using`.

  Um `using` seu seria executado depois que o driver já descartou o stream. Para um `FileStream` ou
  `MemoryStream`, essa segunda chamada é inofensiva, mas para um stream cujo `Dispose` devolve um buffer
  do pool ou reduz uma contagem de referências, o recurso acaba sendo liberado duas vezes.

  A propriedade só é transferida quando os argumentos são aceitos: se a chamada lançar `ArgumentException` ou
  `ArgumentNullException` por table, stream ou format ausente, o stream continua sendo seu.
</Warning>

<Note>
  Consulte a [documentação de configurações de formato](/pt-BR/reference/settings/formats) para ver as opções que controlam o comportamento da ingestão de dados.
</Note>

***

<h3 id="more-examples">
  Mais exemplos
</h3>

Para mais exemplos práticos de uso, consulte o [diretório examples](https://github.com/ClickHouse/clickhouse-cs/tree/main/examples) no repositório do GitHub.

<h2 id="ado-net">
  ADO.NET
</h2>

A biblioteca oferece suporte completo ao ADO.NET por meio de `ClickHouseConnection`, `ClickHouseCommand` e `ClickHouseDataReader`. Essa API é necessária para a integração com ORMs (Dapper, Linq2db) e quando você precisa das abstrações padrão de banco de dados do .NET.

<h3 id="ado-net-datasource">
  Gerenciamento do ciclo de vida com ClickHouseDataSource
</h3>

**Sempre crie conexões a partir de um `ClickHouseDataSource`** para garantir o gerenciamento adequado do ciclo de vida e o uso de pool de conexões. A DataSource gerencia internamente um único `ClickHouseClient`, e todas as conexões compartilham seu pool de conexões HTTP.

```csharp theme={null}
using ClickHouse.Driver.ADO;

// Crie o DataSource uma vez (registre como singleton no DI)
var dataSource = new ClickHouseDataSource("Host=localhost;Username=default;Password=secret");

// Crie conexões leves conforme necessário
await using var connection = await dataSource.OpenConnectionAsync();

// Use a conexão
await using var command = connection.CreateCommand("SELECT version()");
var version = await command.ExecuteScalarAsync();
```

Para injeção de dependências:

```csharp theme={null}
// Em Startup.cs ou Program.cs
services.AddSingleton(sp =>
{
    var factory = sp.GetRequiredService<IHttpClientFactory>();
    return new ClickHouseDataSource("Host=localhost", factory, "ClickHouse");
});

// No seu serviço
public class MyService
{
    private readonly ClickHouseDataSource _dataSource;

    public MyService(ClickHouseDataSource dataSource)
    {
        _dataSource = dataSource;
    }

    public async Task DoWorkAsync()
    {
        await using var connection = await _dataSource.OpenConnectionAsync();
        // Use a conexão...
    }
}
```

<Warning>
  **Não crie `ClickHouseConnection` diretamente** em código de produção. Cada instanciação direta cria um novo cliente HTTP e um novo pool de conexões, o que pode levar ao esgotamento de sockets sob carga:

  ```csharp theme={null}
  // NÃO FAÇA ISSO - cria um novo pool de conexões a cada vez
  using var conn = new ClickHouseConnection("Host=localhost");
  await conn.OpenAsync();
  ```

  Em vez disso, sempre use `ClickHouseDataSource` ou compartilhe uma única instância de `ClickHouseClient`.
</Warning>

***

<h3 id="ado-net-command">
  Usando o ClickHouseCommand
</h3>

Crie comandos usando uma conexão para executar SQL:

```csharp theme={null}
await using var connection = await dataSource.OpenConnectionAsync();

// Criar comando com SQL
await using var command = connection.CreateCommand("SELECT * FROM my_table WHERE id = {id:Int64}");
command.AddParameter("id", 42L);

// Executar e ler resultados
await using var reader = await command.ExecuteReaderAsync();
while (reader.Read())
{
    Console.WriteLine($"Name: {reader.GetString("name")}");
}
```

Métodos de comando:

* `ExecuteNonQueryAsync()` - Para instruções INSERT, UPDATE, DELETE e DDL
* `ExecuteScalarAsync()` - Retorna a primeira coluna da primeira linha
* `ExecuteReaderAsync()` - Retorna um `ClickHouseDataReader` para percorrer os resultados

***

<h3 id="ado-net-reader">
  Usando ClickHouseDataReader
</h3>

O `ClickHouseDataReader` fornece acesso tipado aos resultados da consulta:

```csharp theme={null}
await using var reader = await command.ExecuteReaderAsync();

while (reader.Read())
{
    // Acesso por índice de coluna
    var id = reader.GetInt64(0);
    var name = reader.GetString(1);

    // Acesso por nome de coluna
    var email = reader.GetString("email");

    // Acesso genérico
    var timestamp = reader.GetFieldValue<DateTime>("created_at");

    // Verificar nulo
    if (!reader.IsDBNull("optional_field"))
    {
        var value = reader.GetString("optional_field");
    }
}
```

<h4 id="ado-net-reader-enum-ordinal">
  Lendo o ordinal de um enum
</h4>

Uma coluna `Enum8` ou `Enum16` é materializada como seu label: `GetFieldType` informa `string`, e `GetString`, `GetValue` e `GetFieldValue<string>` retornam o label. Os accessors numéricos lançam `InvalidCastException` em uma coluna enum, porque o valor armazenado é uma string.

Use `TryGetEnumOrdinal` para obter o número por trás do label:

```csharp theme={null}
if (reader.TryGetEnumOrdinal(ordinal, out int value))
    Console.WriteLine(value);   // e.g. 1 for 'Active' in Enum8('Active' = 1)
```

Retorna `true` e define `value` para colunas `Enum8`/`Enum16` e para colunas
`Nullable(Enum...)` cuja célula não seja NULL. Retorna `false`, com `value` definido como `0`, para células NULL ou para
qualquer coluna que não seja um enum. O ordinal é o
valor com sinal obtido do wire, portanto pode ser negativo, e um ordinal `Enum16` pode ser maior que um
byte.

<h2 id="best-practices">
  Boas práticas
</h2>

<h3 id="best-practices-connection-lifetime">
  Ciclo de vida da conexão e pool de conexões
</h3>

`ClickHouse.Driver` usa `System.Net.Http.HttpClient` internamente. O `HttpClient` tem um pool de conexões por endpoint. Como consequência:

* As sessões do banco de dados são multiplexadas por conexões HTTP gerenciadas pelo pool de conexões.
* As conexões HTTP são recicladas automaticamente pelo pool.
* As conexões podem permanecer ativas mesmo depois que os objetos `ClickHouseClient` ou `ClickHouseConnection` são descartados.

**Padrões recomendados:**

| Cenário | Abordagem recomendada |
| - | - |
| Uso geral | Use um `ClickHouseClient` singleton |
| ADO.NET / ORMs | Use `ClickHouseDataSource` (cria conexões que compartilham o mesmo pool) |
| Ambientes de DI | Registre `ClickHouseClient` ou `ClickHouseDataSource` como singleton com `IHttpClientFactory` |

<Warning>
  Ao usar um `HttpClient` ou `HttpClientFactory` personalizado, garanta que `PooledConnectionIdleTimeout` esteja definido com um valor menor que o `keep_alive_timeout` do servidor, para evitar erros causados por conexões parcialmente fechadas. O `keep_alive_timeout` padrão para Implantações no Cloud é de 10 segundos.
</Warning>

<Warning>
  Evite criar várias instâncias de `ClickHouseClient` ou de `ClickHouseConnection` independentes sem um `HttpClient` compartilhado. Cada instância cria seu próprio pool de conexões.
</Warning>

***

<h3 id="best-practice-datetime">
  Tratamento de DateTime
</h3>

1. **Use UTC sempre que possível.** Armazene timestamps como colunas `DateTime('UTC')` e use `DateTimeKind.Utc` no seu código. Isso elimina ambiguidades de fuso horário.

2. **Use `DateTimeOffset` para lidar explicitamente com o fuso horário.** Ele sempre representa um instante específico e inclui a informação de offset.

3. **Especifique o fuso horário nas type hints de SQL.** Ao usar parâmetros com valores `DateTime` `Unspecified` destinados a colunas que não usam UTC, inclua o fuso horário no SQL:
   ```csharp theme={null}
   var parameters = new ClickHouseParameterCollection();
   parameters.AddParameter("dt", myDateTime);

   await client.ExecuteNonQueryAsync(
       "INSERT INTO table (dt) VALUES ({dt:DateTime('Europe/Amsterdam')})",
       parameters
   );
   ```

***

<h3 id="async-inserts">
  Inserções assíncronas
</h3>

[Inserções assíncronas](/pt-BR/concepts/features/operations/insert/asyncinserts) transferem do cliente para o servidor a responsabilidade pelo agrupamento em lotes. Em vez de exigir esse agrupamento no lado do cliente, o servidor armazena em buffer os dados recebidos e os grava no armazenamento com base em limites configuráveis. Isso é útil em cenários de alta concorrência, como workloads de observabilidade, em que muitos agentes enviam payloads pequenos.

Habilite inserções assíncronas via `CustomSettings` ou pela `connection string`:

```csharp theme={null}
// Usando CustomSettings
var settings = new ClickHouseClientSettings("Host=localhost");
settings.CustomSettings["async_insert"] = 1;
settings.CustomSettings["wait_for_async_insert"] = 1; // Recomendado: aguardar confirmação de flush

// Ou via connection string
// "Host=localhost;set_async_insert=1;set_wait_for_async_insert=1"
```

**Dois modos** (controlados por `wait_for_async_insert`):

| Modo | Comportamento | Caso de uso |
| - | - | - |
| `wait_for_async_insert=1` | A inserção retorna depois que os dados são gravados em disco. Os erros são retornados ao cliente. | **Recomendado** para a maioria das workloads |
| `wait_for_async_insert=0` | A inserção retorna imediatamente quando os dados são armazenados no buffer. Não há garantia de que os dados serão persistidos. | Somente quando a perda de dados for aceitável |

<Warning>
  Com `wait_for_async_insert=0`, os erros só aparecem durante o flush e não podem ser rastreados até a inserção original. O cliente também não fornece backpressure, o que pode sobrecarregar o servidor.
</Warning>

**Configurações principais:**

| Configuração | Descrição |
| - | - |
| `async_insert_max_data_size` | Executa o flush quando o buffer atinge este tamanho (bytes) |
| `async_insert_busy_timeout_ms` | Executa o flush após esse timeout (milissegundos) |
| `async_insert_max_query_number` | Executa o flush após esse número de consultas se acumularem |

***

<h3 id="best-practices-sessions">
  Sessões
</h3>

Ative sessões apenas quando precisar de recursos com estado no servidor, por exemplo:

* Tabelas temporárias (`CREATE TEMPORARY TABLE`)
* Manter o contexto da consulta em várias instruções
* Configurações no nível da sessão (`SET max_threads = 4`)

Quando as sessões estão ativadas, as solicitações são serializadas para evitar o uso simultâneo da mesma sessão. Isso adiciona sobrecarga a cargas de trabalho que não exigem estado de sessão.

```csharp theme={null}
var settings = new ClickHouseClientSettings
{
    Host = "localhost",
    UseSession = true,
    SessionId = "my-session", // Optional -- will be auto-generated if not provided
};

using var client = new ClickHouseClient(settings);

await client.ExecuteNonQueryAsync("CREATE TEMPORARY TABLE temp_ids (id UInt64)");
await client.ExecuteNonQueryAsync("INSERT INTO temp_ids VALUES (1), (2), (3)");

var reader = await client.ExecuteReaderAsync(
    "SELECT * FROM users WHERE id IN (SELECT id FROM temp_ids)"
);
```

**Usando ADO.NET (para compatibilidade com ORMs):**

```csharp theme={null}
var settings = new ClickHouseClientSettings
{
    Host = "localhost",
    UseSession = true,
    SessionId = "my-session",
};

var dataSource = new ClickHouseDataSource(settings);
await using var connection = await dataSource.OpenConnectionAsync();

await using var cmd1 = connection.CreateCommand("CREATE TEMPORARY TABLE temp_ids (id UInt64)");
await cmd1.ExecuteNonQueryAsync();

await using var cmd2 = connection.CreateCommand("INSERT INTO temp_ids VALUES (1), (2), (3)");
await cmd2.ExecuteNonQueryAsync();

await using var cmd3 = connection.CreateCommand("SELECT * FROM users WHERE id IN (SELECT id FROM temp_ids)");
await using var reader = await cmd3.ExecuteReaderAsync();
```

***

<h2 id="supported-data-types">
  Tipos de dados compatíveis
</h2>

`ClickHouse.Driver` é compatível com todos os tipos de dados do ClickHouse. As tabelas abaixo mostram o mapeamento entre os tipos do ClickHouse e os tipos nativos do .NET na leitura de dados do banco de dados.

<h3 id="clickhouse-native-type-map-reading">
  Mapeamento de tipos: leitura do ClickHouse
</h3>

<h4 id="type-map-reading-integer">
  Tipos inteiros
</h4>

| Tipo do ClickHouse | Tipo do .NET |
| - | - |
| Int8 | `sbyte` |
| UInt8 | `byte` |
| Int16 | `short` |
| UInt16 | `ushort` |
| Int32 | `int` |
| UInt32 | `uint` |
| Int64 | `long` |
| UInt64 | `ulong` |
| Int128 | `BigInteger` |
| UInt128 | `BigInteger` |
| Int256 | `BigInteger` |
| UInt256 | `BigInteger` |

***

<h4 id="type-map-reading-floating-points">
  Tipos de ponto flutuante
</h4>

| Tipo do ClickHouse | Tipo .NET |
| - | - |
| Float32 | `float` |
| Float64 | `double` |
| BFloat16 | `float` |

***

<h4 id="type-map-reading-decimal">
  Tipos decimais
</h4>

| Tipo do ClickHouse | Tipo .NET |
| - | - |
| Decimal(P, S) | `decimal` / `ClickHouseDecimal` |
| Decimal32(S) | `decimal` / `ClickHouseDecimal` |
| Decimal64(S) | `decimal` / `ClickHouseDecimal` |
| Decimal128(S) | `decimal` / `ClickHouseDecimal` |
| Decimal256(S) | `decimal` / `ClickHouseDecimal` |

<Note>
  A conversão de tipos decimais é controlada pela configuração UseCustomDecimals.
</Note>

***

<h4 id="type-map-reading-boolean">
  Tipo booleano
</h4>

| Tipo do ClickHouse | Tipo .NET |
| - | - |
| Bool | `bool` |

***

<h4 id="type-map-reading-strings">
  Tipos String
</h4>

| Tipo do ClickHouse | Tipo .NET |
| - | - |
| String | `string` |
| FixedString(N) | `string` |

<Note>
  Por padrão, as colunas `String` e `FixedString(N)` são retornadas como `string`. Defina `ReadStringsAsByteArrays=true` na string de conexão para lê-las como `byte[]`. Isso é útil ao armazenar dados binários que podem não estar em UTF-8 válido.

  A configuração também se aplica a strings aninhadas dentro de outros tipos, de modo que `Array(String)` é lido como `byte[][]`
  e `Map(String, String)` como `Dictionary<byte[], byte[]>` — inclusive as chaves. A única exceção é uma
  coluna `JSON`, cujas folhas de string são sempre texto; veja [Tipo JSON](#type-map-reading-json).
</Note>

***

<h4 id="type-map-reading-datetime">
  Tipos de data e hora
</h4>

| Tipo do ClickHouse | Tipo .NET |
| - | - |
| Date | `DateTime` |
| Date32 | `DateTime` |
| DateTime | `DateTime` |
| DateTime32 | `DateTime` |
| DateTime64 | `DateTime` |
| Time | `TimeSpan` |
| Time64 | `TimeSpan` |

O ClickHouse armazena internamente os valores `DateTime` e `DateTime64` como timestamps Unix (segundos ou frações de segundo desde a epoch). Embora o armazenamento seja sempre em UTC, as colunas podem ter um fuso horário associado, o que afeta como os valores são exibidos e interpretados.

Ao ler valores `DateTime`, a propriedade `DateTime.Kind` é definida com base no fuso horário da coluna:

| Definição da coluna | `DateTime.Kind` retornado | Observações |
| - | - | - |
| `DateTime('UTC')` | `Utc` | Fuso horário UTC explícito |
| `DateTime('Europe/Amsterdam')` | `Unspecified` | Deslocamento aplicado |
| `DateTime` | `Unspecified` | Hora local preservada como está |

Para colunas que não estão em UTC, o `DateTime` retornado representa a hora local nesse fuso horário. Use `ClickHouseDataReader.GetDateTimeOffset()` para obter um `DateTimeOffset` com o deslocamento correto para esse fuso horário:

```csharp theme={null}
var reader = (ClickHouseDataReader)await connection.ExecuteReaderAsync(
    "SELECT toDateTime('2024-06-15 14:30:00', 'Europe/Amsterdam')");
reader.Read();

var dt = reader.GetDateTime(0);    // 2024-06-15 14:30:00, Kind=Unspecified
var dto = reader.GetDateTimeOffset(0); // 2024-06-15 14:30:00 +02:00 (CEST)
```

Para colunas **sem** um fuso horário explícito (ou seja, `DateTime` em vez de `DateTime('Europe/Amsterdam')`), o driver retorna um `DateTime` com `Kind=Unspecified`. Isso preserva exatamente a hora local como foi armazenada, sem fazer suposições sobre o fuso horário.

Se você precisar de um comportamento sensível a fuso horário para colunas sem fusos horários explícitos, faça uma destas opções:

1. Use fusos horários explícitos nas definições das colunas: `DateTime('UTC')` ou `DateTime('Europe/Amsterdam')`
2. Aplique o fuso horário manualmente após a leitura.

***

<h4 id="type-map-reading-json">
  Tipo JSON
</h4>

| Tipo ClickHouse | Tipo .NET | Observações |
| - | - | - |
| Json | `JsonObject` | Padrão (`JsonReadMode=Binary`) |
| Json | `string` | Quando `JsonReadMode=String` |

O tipo de retorno das colunas JSON é controlado pela configuração `JsonReadMode`:

* **`Binary` (padrão)**: Retorna `System.Text.Json.Nodes.JsonObject`. Fornece acesso estruturado aos dados JSON, mas tipos especializados do ClickHouse (como endereços IP, UUIDs e valores decimais grandes) são convertidos para suas representações em string dentro da estrutura JSON.

* **`String`**: Retorna o JSON bruto como `string`. Preserva a representação exata do JSON no ClickHouse, o que é útil quando você precisa repassar o JSON sem fazer o parsing ou quando deseja cuidar da desserialização por conta própria.

```csharp theme={null}
// Configure string mode via settings
var settings = new ClickHouseClientSettings("Host=localhost")
{
    JsonReadMode = JsonReadMode.String
};

// Or via connection string
// "Host=localhost;JsonReadMode=String"
```

`None` é um terceiro modo. Ele faz a leitura exatamente como o `Binary`, mas não envia nenhuma server setting junto com a
consulta — use-o em uma connection que não tem permissão para definir uma.

<h5 id="type-map-reading-json-nulls">
  Typed paths e nulls
</h5>

Um path declarado no column type é um **typed path**; qualquer outro path do documento é um
**dynamic path**. Os dois se diferenciam quando o valor é nulo.

Um typed path sempre aparece no `JsonObject`. Declarado como `Nullable(T)` ou `Dynamic`, ele é retornado
como um JSON null tanto quando o valor armazenado é nulo quanto quando o documento não possui esse path — os dois
casos são indistinguíveis:

```csharp theme={null}
// Column type JSON(x Nullable(Int64))
// stored '{"x":null}'  ->  {"x":null}
// stored '{}'          ->  {"x":null}
```

Declarado com um tipo não Nullable, um caminho ausente assume o valor padrão do tipo — `JSON(x String)`
resulta em `{"x":""}` e `JSON(x Int64)` resulta em `{"x":0}`.

Um caminho dinâmico cujo valor é nulo é removido por completo do objeto, de modo que `ContainsKey` retorna
false para ele. Ler `{"x":null}` de uma coluna `JSON` simples resulta em `{}`.

Caminhos tipados aninhados criam seus parents, portanto `JSON(a.b Nullable(Int64))` produz `{"a":{"b":null}}`
mesmo para um documento vazio.

<Note>
  É isso que o próprio servidor renderiza, portanto os modes `Binary` e `String` agora coincidem. Antes da 1.4.0, um
  caminho tipado contendo null era removido do `JsonObject`, o que fazia `{"x":null}` ser lido como
  `{}` — e, para um caminho aninhado como `JSON(a.b Nullable(Int64))`, toda a subárvore `a` desaparecia.
</Note>

<h5 id="type-map-reading-json-strings">
  Strings dentro de uma coluna JSON
</h5>

Folhas de string dentro de uma coluna JSON são sempre retornadas como texto, qualquer que seja o
valor de `ReadStringsAsByteArrays` — `JsonValue` não possui uma forma de array de bytes, portanto um `byte[]` seria
renderizado como base64. Isso vale para `String`, `FixedString` e para os tipos encapsulados em
`LowCardinality`, `Nullable` ou `SimpleAggregateFunction`, além das strings dentro de `Array` e `Map`,
incluindo as chaves do map.

<Note>
  Um array de bytes cujo tipo o leitor JSON não consegue identificar continua sendo renderizado como base64: um
  typed path `Variant` ou `Dynamic` guarda um valor cujo tipo só é conhecido linha a linha, então uma string
  sob `Variant(Array(UInt8), String)` retorna codificada em Base64. Isso ocorre da mesma forma em ambas as configurações.

  Um tipo de chave de map JSON que não seja exatamente `String` — `Map(LowCardinality(String), String)`, por
  exemplo — lança `NotSupportedException`.
</Note>

<h5 id="overlapping-paths">
  Caminhos sobrepostos
</h5>

O ClickHouse aceita uma coluna que declara um caminho tanto como valor quanto como pai de outro
caminho, por exemplo `JSON(a Int64, a.b Int64)`. Ambos os caminhos estão presentes em todas as linhas, portanto o servidor
renderiza a linha com uma chave duplicada: `{"a":0,"a":{"b":7}}`. Um `JsonObject` não pode conter dois valores
para uma mesma chave, então `JsonReadMode.Binary` lança uma `SerializationException` indicando os dois caminhos. O
mesmo vale quando o valor é um `Map`, como em `JSON(a Map(String, Int64))` lido de uma linha que
também tem um `a.b` dinâmico.

Isso se aplica apenas quando ambos os lados contêm um valor naquela linha. O lado que não contém nada — um null,
um objeto vazio ou uma subárvore cujos valores são todos null — cede lugar ao lado que tem os dados,
qualquer que seja o caminho enviado primeiro pelo servidor. Portanto, uma sobreposição declarada com tipos `Nullable` preenche um lado por linha e é lida sem
erro: `JSON(a Nullable(Int64), a.b Nullable(Int64))` produz `{"a":5}` e `{"a":{"b":7}}`, conforme
esperado.

Leia essa coluna com `JsonReadMode.String` para obter o texto JSON do servidor inalterado, incluindo a chave
duplicada.

Defina `AllowDuplicateJsonKeys` para continuar lendo a coluna como um `JsonObject` em vez de lançar uma exceção. Nesse caso, o
driver mantém, entre os dois, o valor que vier por último na linha e descarta o outro, de modo que o
resultado é com perda: `JSON(a Int64, a.b Int64)` contendo `{"a.b":7}` é lido como `{"a":0}`. Um caminho que
contém um valor e cujo pai contém um scalar ou um array continua lançando exceção, porque não há como
colocar uma subárvore sob nenhum dos dois.

```csharp theme={null}
var settings = new ClickHouseClientSettings("Host=localhost")
{
    AllowDuplicateJsonKeys = true
};

// Or via connection string
// "Host=localhost;AllowDuplicateJsonKeys=true"
```

***

<h4 id="type-map-reading-map">
  Map type
</h4>

| ClickHouse Type | .NET Type | Notes |
| - | - | - |
| Map(K, V) | `Dictionary<K, V>` | Padrão (`MapReadMode=Dictionary`) |
| Map(K, V) | `List<KeyValuePair<K, V>>` | Quando `MapReadMode=KeyValuePairs` |

Um `Map(K, V)` do ClickHouse é fisicamente um `Array(Tuple(K, V))` e pode conter várias entradas com a mesma chave. Um `Dictionary` não pode; por isso, no modo padrão, uma chave repetida mantém apenas o último valor e os pares anteriores são descartados. A configuração `MapReadMode` define a representação:

* **`Dictionary` (padrão)**: retorna `Dictionary<K, V>`.

* **`KeyValuePairs`**: retorna `List<KeyValuePair<K, V>>` na ordem em que o servidor enviou os pares, preservando todos eles, inclusive as entradas que repetem uma chave.

```csharp theme={null}
// Configure key-value-pair mode via settings
var settings = new ClickHouseClientSettings("Host=localhost")
{
    MapReadMode = MapReadMode.KeyValuePairs
};

// Or via connection string
// "Host=localhost;MapReadMode=KeyValuePairs"
```

O mode seleciona o tipo de framework de uma coluna `Map`, portanto também se aplica a `GetFieldValue<T>`, aos tipos de schema que o driver informa e ao mapeamento de propriedades POCO. Ele vale onde quer que um map apareça na árvore de tipos de uma coluna — incluindo `Array(Map(...))`, `Map(K, Map(...))`, `Tuple(..., Map(...))` e `Dynamic`.

Ambas as representações são aceitas no caminho de gravação em qualquer um dos modes — consulte [gravação de maps](#type-map-writing-other).

***

<h4 id="type-map-reading-other">
  Outros tipos
</h4>

| Tipo do ClickHouse | Tipo .NET |
| - | - |
| UUID | `Guid` |
| IPv4 | `IPAddress` |
| IPv6 | `IPAddress` |
| Nothing | `DBNull` |
| Dynamic | Consulte a nota |
| Array(T) | `T[]` (`Array(Array(T))` aninhado é lido como `T[][]` irregular; use `reader.GetFieldValue<T[,]>(ordinal)` para materializar dados retangulares como uma matriz CLR multidimensional) |
| Tuple(T1, T2, ...) | `Tuple<T1, T2, ...>` / `LargeTuple` |
| Map(K, V) | `Dictionary<K, V>`, ou `List<KeyValuePair<K, V>>` quando `MapReadMode=KeyValuePairs` — consulte [Map type](#type-map-reading-map) |
| Nullable(T) | `T?` |
| Enum8 | `string` |
| Enum16 | `string` |
| LowCardinality(T) | O mesmo que T |
| SimpleAggregateFunction | O mesmo que o tipo subjacente |
| Nested(...) | `Tuple[]` |
| Variant(T1, T2, ...) | Consulte a nota |
| QBit(T, dimension) | `T[]` |

<Note>
  Os tipos Dynamic e Variant serão convertidos para o tipo correspondente ao tipo subjacente real de cada linha.
</Note>

***

<h4 id="type-map-reading-geometry">
  Tipos de geometria
</h4>

| Tipo ClickHouse | Tipo .NET |
| - | - |
| Point | `Tuple<double, double>` |
| Ring | `Tuple<double, double>[]` |
| LineString | `Tuple<double, double>[]` |
| Polygon | `Ring[]` |
| MultiLineString | `LineString[]` |
| MultiPolygon | `Polygon[]` |
| Geometry | Consulte a observação |

<Note>
  O tipo Geometry é um Variant que pode conter qualquer um dos tipos de geometria. Ele será convertido para o tipo correspondente.
</Note>

***

<h3 id="clickhouse-native-type-map-writing">
  Mapeamento de tipos: escrita no ClickHouse
</h3>

Ao inserir dados, o driver converte tipos .NET nos tipos correspondentes do ClickHouse. As tabelas abaixo mostram quais tipos .NET são aceitos para cada tipo de coluna do ClickHouse.

<h4 id="type-map-writing-integer">
  Tipos inteiros
</h4>

| Tipo do ClickHouse | Tipos .NET aceitos | Observações |
| - | - | - |
| Int8 | `sbyte`, qualquer tipo compatível com `Convert.ToSByte()` | |
| UInt8 | `byte`, qualquer tipo compatível com `Convert.ToByte()` | |
| Int16 | `short`, qualquer tipo compatível com `Convert.ToInt16()` | |
| UInt16 | `ushort`, qualquer tipo compatível com `Convert.ToUInt16()` | |
| Int32 | `int`, qualquer tipo compatível com `Convert.ToInt32()` | |
| UInt32 | `uint`, qualquer tipo compatível com `Convert.ToUInt32()` | |
| Int64 | `long`, qualquer tipo compatível com `Convert.ToInt64()` | |
| UInt64 | `ulong`, qualquer tipo compatível com `Convert.ToUInt64()` | |
| Int128 | `BigInteger`, `decimal`, `double`, `float`, `int`, `uint`, `long`, `ulong`, qualquer tipo compatível com `Convert.ToInt64()` | |
| UInt128 | `BigInteger`, `decimal`, `double`, `float`, `int`, `uint`, `long`, `ulong`, qualquer tipo compatível com `Convert.ToInt64()` | |
| Int256 | `BigInteger`, `decimal`, `double`, `float`, `int`, `uint`, `long`, `ulong`, qualquer tipo compatível com `Convert.ToInt64()` | |
| UInt256 | `BigInteger`, `decimal`, `double`, `float`, `int`, `uint`, `long`, `ulong`, qualquer tipo compatível com `Convert.ToInt64()` | |

***

<h4 id="type-map-writing-floating-point">
  Tipos de ponto flutuante
</h4>

| Tipo do ClickHouse | Tipos .NET aceitos | Observações |
| - | - | - |
| Float32 | `float`, qualquer tipo compatível com `Convert.ToSingle()` | |
| Float64 | `double`, qualquer tipo compatível com `Convert.ToDouble()` | |
| BFloat16 | `float`, qualquer tipo compatível com `Convert.ToSingle()` | Trunca para o formato BFloat16 de 16 bits |

***

<h4 id="type-map-writing-boolean">
  Tipo booleano
</h4>

| Tipo do ClickHouse | Tipos .NET aceitos | Observações |
| - | - | - |
| Bool | `bool` | |

***

<h4 id="type-map-writing-strings">
  Tipos String
</h4>

| Tipo do ClickHouse | Tipos .NET aceitos | Observações |
| - | - | - |
| String | `string`, `byte[]`, `ReadOnlyMemory<byte>`, `Stream` | Tipos binários são gravados diretamente; streams podem ter ou não suporte a seek |
| FixedString(N) | `string`, `byte[]`, `ReadOnlyMemory<byte>`, `Stream` | A string é codificada em UTF-8 e preenchida; os tipos binários devem ter exatamente N bytes |

***

<h4 id="type-map-writing-datetime">
  Tipos de data e hora
</h4>

| Tipo do ClickHouse | Tipos .NET aceitos | Observações |
| - | - | - |
| Date | `DateTime`, `DateTimeOffset`, `DateOnly`, tipos NodaTime | Convertido em dias Unix como UInt16; intervalo suportado `[1970-01-01, 2149-06-06]` |
| Date32 | `DateTime`, `DateTimeOffset`, `DateOnly`, tipos NodaTime | Convertido em dias Unix como Int32; intervalo suportado `[1900-01-01, 2299-12-31]` |
| DateTime | `DateTime`, `DateTimeOffset`, `DateOnly`, tipos NodaTime | Veja abaixo mais detalhes; intervalo suportado `[1970-01-01, 2106-02-07 06:28:15]` UTC |
| DateTime32 | `DateTime`, `DateTimeOffset`, `DateOnly`, tipos NodaTime | Igual a DateTime |
| DateTime64 | `DateTime`, `DateTimeOffset`, `DateOnly`, tipos NodaTime | Precisão baseada no parâmetro de escala |
| Time | `TimeSpan`, `TimeOnly`, `int` | Limitado a ±999:59:59; `int` é tratado como segundos |
| Time64 | `TimeSpan`, `TimeOnly`, `decimal`, `double`, `float`, `int`, `long`, `string` | `string` é interpretada como `[-]HHH:MM:SS[.fraction]`; limitado a ±999:59:59.999999999 |

<Note>
  **Valores fora do intervalo**

  No caminho de gravação binária, valores de `Date`, `Date32`, `DateTime` e `DateTime32` fora do intervalo suportado lançam `ArgumentOutOfRangeException` no momento de `Write`, indicando o tipo da coluna e o intervalo suportado. Anteriormente, valores fora do intervalo podiam ser truncados silenciosamente por meio de um inteiro de 32 bits e reinterpretados pelo servidor, produzindo timestamps reais, mas incorretos.
</Note>

O driver respeita `DateTime.Kind` ao gravar valores:

| DateTime.Kind | Parâmetros HTTP | Em massa |
| - | - | - |
| Utc | Instante preservado | Instante preservado |
| Local | Instante preservado | Instante preservado |
| Unspecified | Tratado como hora local no fuso horário do tipo do parâmetro (o padrão é UTC) | Tratado como hora local no fuso horário da coluna |

Os valores de `DateTimeOffset` sempre preservam o instante exato.

**Exemplo: DateTime UTC (instante preservado)**

```csharp theme={null}
var utcTime = new DateTime(2024, 1, 15, 12, 0, 0, DateTimeKind.Utc);
// Stored as 12:00 UTC
// Read from DateTime('Europe/Amsterdam') column: 13:00 (UTC+1)
// Read from DateTime('UTC') column: 12:00 UTC
```

**Exemplo: DateTime não especificado (hora local)**

```csharp theme={null}
var wallClock = new DateTime(2024, 1, 15, 14, 30, 0, DateTimeKind.Unspecified);
// Written to DateTime('Europe/Amsterdam') column: stored as 14:30 Amsterdam time
// Read back from DateTime('Europe/Amsterdam') column: 14:30
```

**Recomendação:** para obter o comportamento mais simples e previsível, use `DateTimeKind.Utc` ou `DateTimeOffset` em todas as operações com DateTime. Isso garante que seu código funcione de forma consistente, independentemente do fuso horário do servidor, do cliente ou da coluna.

<h4 id="datetime-http-param-vs-bulkcopy">
  Parâmetros HTTP vs bulk copy
</h4>

Há uma diferença importante entre a vinculação de parâmetros HTTP e o bulk copy ao gravar valores `Unspecified` de DateTime:

**Bulk Copy** conhece o fuso horário da coluna de destino e interpreta corretamente os valores `Unspecified` nesse fuso.

**Parâmetros HTTP** não conhecem automaticamente o fuso horário da coluna. Você deve especificá-lo na dica de tipo SQL:

```csharp theme={null}
// CORRETO: Fuso horário no type hint SQL - o tipo é extraído automaticamente
command.CommandText = "INSERT INTO table (dt_amsterdam) VALUES ({dt:DateTime('Europe/Amsterdam')})";
command.AddParameter("dt", myDateTime);

// INCORRETO: Sem o type hint de fuso horário, interpretado como UTC
command.CommandText = "INSERT INTO table (dt_amsterdam) VALUES ({dt:DateTime})";
command.AddParameter("dt", myDateTime);
// O valor String "2024-01-15 14:30:00" é interpretado como UTC, não como horário de Amsterdã!
```

| `DateTime.Kind` | Coluna de destino | Parâmetro HTTP (com indicação de fuso horário) | Parâmetro HTTP (sem indicação de fuso horário) | Bulk Copy |
| - | - | - | - | - |
| `Utc` | UTC | Instante preservado | Instante preservado | Instante preservado |
| `Utc` | Europe/Amsterdam | Instante preservado | Instante preservado | Instante preservado |
| `Local` | Qualquer | Instante preservado | Instante preservado | Instante preservado |
| `Unspecified` | UTC | Interpretado como UTC | Interpretado como UTC | Interpretado como UTC |
| `Unspecified` | Europe/Amsterdam | Interpretado como horário de Amsterdã | **Interpretado como UTC** | Interpretado como horário de Amsterdã |

***

<h4 id="type-map-writing-decimal">
  Tipos Decimal
</h4>

| Tipo do ClickHouse | Tipos .NET aceitos | Observações |
| - | - | - |
| Decimal(P,S) | `decimal`, `ClickHouseDecimal`, qualquer tipo compatível com `Convert.ToDecimal()` | Lança `OverflowException` se exceder a precisão |
| Decimal32 | `decimal`, `ClickHouseDecimal`, qualquer tipo compatível com `Convert.ToDecimal()` | Precisão máxima: 9 |
| Decimal64 | `decimal`, `ClickHouseDecimal`, qualquer tipo compatível com `Convert.ToDecimal()` | Precisão máxima: 18 |
| Decimal128 | `decimal`, `ClickHouseDecimal`, qualquer tipo compatível com `Convert.ToDecimal()` | Precisão máxima: 38 |
| Decimal256 | `decimal`, `ClickHouseDecimal`, qualquer tipo compatível com `Convert.ToDecimal()` | Precisão máxima: 76 |

***

<h4 id="type-map-writing-json">
  Tipo JSON
</h4>

| Tipo ClickHouse | Tipos .NET aceitos | Observações |
| - | - | - |
| Json | `string`, `JsonObject`, `JsonNode`, qualquer objeto | O comportamento depende da configuração `JsonWriteMode` |

O comportamento ao escrever JSON é controlado pela configuração `JsonWriteMode`:

| Tipo de entrada | `JsonWriteMode.String` (padrão) | `JsonWriteMode.Binary` |
| - | - | - |
| `string` | Passado diretamente | Lança `ArgumentException` |
| `JsonObject` | Serializado com `ToJsonString()` | Lança `ArgumentException` |
| `JsonNode` | Serializado com `ToJsonString()` | Lança `ArgumentException` |
| POCO registrado | Serializado com `JsonSerializer.Serialize()` | Codificação binária com suporte a dicas de tipo e atributos de caminho personalizados |
| POCO não registrado / objeto anônimo | Serializado com `JsonSerializer.Serialize()` | Lança `ClickHouseJsonSerializationException` |

* **`String` (padrão)**: Aceita `string`, `JsonObject`, `JsonNode` ou qualquer objeto. Todas as entradas são serializadas com `System.Text.Json.JsonSerializer` e enviadas como strings JSON para processamento no servidor. Este é o modo mais flexível e funciona sem registro de tipo.

* **`Binary`**: Aceita apenas tipos POCO registrados. Os dados são convertidos no cliente para o formato JSON binário do ClickHouse, com suporte completo a dicas de tipo. Requer chamar `connection.RegisterJsonSerializationType<T>()` antes do uso. Escrever valores `string` ou `JsonNode` nesse modo lança `ArgumentException`.

```csharp theme={null}
// O modo String padrão funciona com qualquer entrada
await client.InsertBinaryAsync(
    "my_table",
    new[] { "id", "data" },
    new[] { new object[] { 1u, new { name = "test", value = 42 } } }
);

// O modo Binary requer habilitação explícita e registro de tipo
var settings = new ClickHouseClientSettings("Host=localhost")
{
    JsonWriteMode = JsonWriteMode.Binary
};
using var client = new ClickHouseClient(settings);
client.RegisterJsonSerializationType<MyPocoType>();
```

<h5 id="json-typed-columns">
  Colunas JSON tipadas
</h5>

Quando uma coluna JSON tem dicas de tipo (por exemplo, `JSON(id UInt64, price Decimal128(2))`), o driver usa essas dicas para serializar valores com total fidelidade aos tipos. Isso preserva a precisão de tipos como `UInt64`, `Decimal`, `UUID` e `DateTime64`, que, de outra forma, perderiam precisão ao serem serializados como JSON genérico.

<h5 id="json-poco-serialization">
  Serialização de POCO
</h5>

POCOs podem ser gravados em colunas JSON de duas formas, dependendo do `JsonWriteMode`:

**Modo String (padrão)**: os POCOs são serializados por meio de `System.Text.Json.JsonSerializer`. Não é necessário registrar tipos. Esta é a abordagem mais simples e funciona com objetos anônimos.

**Modo binário**: os POCOs são serializados usando o formato JSON binário do driver, com suporte completo a type hints. Os tipos devem ser registrados com `connection.RegisterJsonSerializationType<T>()` antes do uso. Esse modo oferece suporte a mapeamentos de path personalizados por meio de atributos:

* **`[ClickHouseJsonPath("path")]`**: Mapeia uma propriedade para um path JSON personalizado. Útil para estruturas aninhadas ou quando o nome da propriedade difere da chave JSON desejada. **Funciona apenas no modo binário.**

* **`[ClickHouseJsonIgnore]`**: Exclui uma propriedade da serialização. **Funciona apenas no modo binário.**

```sql theme={null}
CREATE TABLE events (
    id UInt32,
    data JSON(`user.id` Int64, `user.name` String, Timestamp DateTime64(3))
) ENGINE = MergeTree() ORDER BY id
```

```csharp theme={null}
using ClickHouse.Driver.Json;

public class UserEvent
{
    [ClickHouseJsonPath("user.id")]
    public long UserId { get; set; }

    [ClickHouseJsonPath("user.name")]
    public string UserName { get; set; }

    public DateTime Timestamp { get; set; }

    [ClickHouseJsonIgnore]
    public string InternalData { get; set; }  // Não é serializado
}

// No modo Binary: registre o tipo e habilite o modo Binary
var settings = new ClickHouseClientSettings("Host=localhost") { JsonWriteMode = JsonWriteMode.Binary };
using var client = new ClickHouseClient(settings);
client.RegisterJsonSerializationType<UserEvent>();

// Inserir POCO - serializado em JSON com estrutura aninhada por meio de atributos de caminho personalizados
await client.InsertBinaryAsync(
    "events",
    new[] { "id", "data" },
    new[] { new object[] { 1u, new UserEvent { UserId = 123, UserName = "Alice", Timestamp = DateTime.UtcNow } } }
);
// JSON resultante: {"user": {"id": 123, "name": "Alice"}, "Timestamp": "2024-01-15T..."}
```

A correspondência entre o nome da propriedade e as dicas de tipo da coluna diferencia maiúsculas de minúsculas. Uma propriedade `UserId` só corresponderá a uma dica definida como `UserId`, não como `userid`. Isso está de acordo com o comportamento do ClickHouse, que permite que caminhos como `userName` e `UserName` coexistam como campos separados.

**Limitações (apenas no modo Binary):**

* Os tipos POCO precisam ser registrados na conexão com `connection.RegisterJsonSerializationType<T>()` antes da serialização. Tentar serializar um tipo não registrado lança `ClickHouseJsonSerializationException`.
* Propriedades de Dicionário e array/lista exigem dicas de tipo na definição da coluna para serem serializadas corretamente. Sem essas dicas, use o modo String.
* Valores nulos em propriedades POCO só são gravados quando o caminho tem uma dica de tipo `Nullable(T)` na definição da coluna. O ClickHouse não permite tipos `Nullable` em caminhos JSON dinâmicos, portanto propriedades nulas sem dica são ignoradas.
* Os atributos `ClickHouseJsonPath` e `ClickHouseJsonIgnore` são ignorados no modo String (eles só funcionam no modo Binary).

***

<h4 id="type-map-writing-other">
  Outros tipos
</h4>

| ClickHouse Type | Accepted .NET Types | Notas |
| - | - | - |
| UUID | `Guid`, `string` | `string` é analisada como `Guid` |
| IPv4 | `IPAddress`, `string` | Deve ser IPv4; `string` é analisada via `IPAddress.Parse()` |
| IPv6 | `IPAddress`, `string` | Deve ser IPv6; `string` é analisada via `IPAddress.Parse()` |
| Nothing | Qualquer | Não grava nada (no-op) |
| Dynamic | — | **Sem suporte** (lança `NotImplementedException`) |
| Array(T) | `IList`, `null` | `null` grava um array vazio. Para tipos Nested (`Array(Array(T))` e mais profundos), são aceitos tanto formatos irregulares (`T[][]`, `List<List<T>>`) quanto arrays CLR multidimensionais retangulares (`T[,]`, `T[,,]`, …); o rank do CLR deve corresponder à profundidade de aninhamento no ClickHouse. |
| Tuple(T1, T2, ...) | `ITuple`, `IList` | A quantidade de elementos deve corresponder à aridade da tupla. Veja a [ressalva sobre ValueTuple](#valuetuple-caveat) para mais de 7 elementos. |
| Map(K, V) | `IDictionary`, `IEnumerable<KeyValuePair<K, V>>` | Uma sequência de pares (por exemplo, a `List<KeyValuePair<K, V>>` produzida por `MapReadMode=KeyValuePairs`) é aceita em qualquer modo de leitura e pode repetir uma chave. Aplica-se a inserts binários e a query parameters |
| Nullable(T) | `null`, `DBNull`, ou tipos aceitos por T | Grava o byte indicador de null antes do valor |
| Enum8 | `string`, `sbyte`, tipos numéricos | `string` é procurada no dicionário do enum |
| Enum16 | `string`, `short`, tipos numéricos | `string` é procurada no dicionário do enum |
| LowCardinality(T) | Tipos aceitos por T | Delega ao tipo subjacente |
| SimpleAggregateFunction | Tipos aceitos pelo tipo subjacente | Delega ao tipo subjacente |
| Nested(...) | `IList` de tuplas | A quantidade de elementos deve corresponder à quantidade de campos |
| Variant(T1, T2, ...) | Valor correspondente a um de T1, T2, ... | Lança `ArgumentException` se não houver correspondência de tipo |
| QBit(T, dim) | `IList` | Delega a Array; a dimensão é apenas metadado |

***

<h4 id="type-map-writing-geometry">
  Tipos de geometria
</h4>

| Tipo do ClickHouse | Tipos .NET aceitos | Observações |
| - | - | - |
| Point | `System.Drawing.Point`, `ITuple`, `IList` (2 elementos) | |
| Ring | `IList` de Point | |
| LineString | `IList` de Point | |
| Polygon | `IList` de Ring | |
| MultiLineString | `IList` de LineString | |
| MultiPolygon | `IList` de Polygon | |
| Geometry | Qualquer tipo de geometria acima | Variant de todos os tipos de geometria |

***

<h4 id="type-map-writing-not-supported">
  Não suportado para escrita
</h4>

| Tipo do ClickHouse | Notas |
| - | - |
| Dynamic | Lança `NotImplementedException` |
| AggregateFunction | Lança `AggregateFunctionException` |

***

<h3 id="nested-type-handling">
  Tratamento do tipo Nested
</h3>

Os tipos aninhados do ClickHouse (`Nested(...)`) podem ser lidos e gravados usando semântica de arrays.

```sql theme={null}
CREATE TABLE test.nested (
    id UInt32,
    params Nested (param_id UInt8, param_val String)
) ENGINE = Memory
```

```csharp theme={null}
var row1 = new object[] { 1, new[] { 1, 2, 3 }, new[] { "v1", "v2", "v3" } };
var row2 = new object[] { 2, new[] { 4, 5, 6 }, new[] { "v4", "v5", "v6" } };

await client.InsertBinaryAsync(
    "test.nested",
    new[] { "id", "params.param_id", "params.param_val" },
    new[] { row1, row2 }
);
```

<h2 id="logging-and-diagnostics">
  Logging e diagnósticos
</h2>

O cliente .NET do ClickHouse se integra às abstrações `Microsoft.Extensions.Logging` para oferecer logging leve e opcional. Quando habilitado, o driver emite mensagens estruturadas para eventos do ciclo de vida da conexão, execução de comandos, operações de transporte e operações de inserção em massa. O logging é totalmente opcional — aplicações que não configuram um logger continuam em execução sem sobrecarga adicional.

<h3 id="logging-quick-start">
  Início rápido
</h3>

```csharp theme={null}
using ClickHouse.Driver;
using Microsoft.Extensions.Logging;

var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .AddConsole()
        .SetMinimumLevel(LogLevel.Information);
});

var settings = new ClickHouseClientSettings("Host=localhost;Port=8123")
{
    LoggerFactory = loggerFactory
};

using var client = new ClickHouseClient(settings);
```

<h4 id="logging-appsettings-config">
  Usando appsettings.json
</h4>

Você pode configurar os níveis de log usando a configuração padrão do .NET:

```csharp theme={null}
using ClickHouse.Driver;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

var configuration = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json")
    .Build();

var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .AddConfiguration(configuration.GetSection("Logging"))
        .AddConsole();
});

var settings = new ClickHouseClientSettings("Host=localhost;Port=8123")
{
    LoggerFactory = loggerFactory
};

using var client = new ClickHouseClient(settings);
```

<h4 id="logging-inmemory-config">
  Usando configuração em memória
</h4>

Você também pode configurar o nível de verbosidade do logging por categoria no código:

```csharp theme={null}
using ClickHouse.Driver;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

var categoriesConfiguration = new Dictionary<string, string>
{
    { "LogLevel:Default", "Warning" },
    { "LogLevel:ClickHouse.Driver.Connection", "Information" },
    { "LogLevel:ClickHouse.Driver.Command", "Debug" }
};

var config = new ConfigurationBuilder()
    .AddInMemoryCollection(categoriesConfiguration)
    .Build();

using var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .AddConfiguration(config)
        .AddSimpleConsole();
});

var settings = new ClickHouseClientSettings("Host=localhost;Port=8123")
{
    LoggerFactory = loggerFactory
};

using var client = new ClickHouseClient(settings);
```

<h3 id="logging-categories">
  Categorias e emissores
</h3>

O driver usa categorias específicas para que você possa ajustar com precisão os níveis de log por componente:

| Categoria | Origem | Destaques |
| - | - | - |
| `ClickHouse.Driver.Connection` | `ClickHouseConnection` | Ciclo de vida da conexão, seleção da fábrica de clientes HTTP, abertura/fechamento de conexão e gerenciamento de sessão. |
| `ClickHouse.Driver.Command` | `ClickHouseCommand` | Início/conclusão da execução da consulta, temporização, IDs de consulta, estatísticas do servidor e detalhes de erro. |
| `ClickHouse.Driver.Transport` | `ClickHouseConnection` | Requisições HTTP streaming de baixo nível, sinalizadores de compressão, códigos de status da resposta e falhas de transporte. |
| `ClickHouse.Driver.Client` | `ClickHouseClient` | Insert binário, consultas e outras operações |
| `ClickHouse.Driver.NetTrace` | `TraceHelper` | Rastreamento de rede, somente quando o modo de depuração está habilitado |

<h4 id="logging-config-example">
  Exemplo: Diagnóstico de problemas de conexão
</h4>

```json theme={null}
{
    "Logging": {
        "LogLevel": {
            "ClickHouse.Driver.Connection": "Trace",
            "ClickHouse.Driver.Transport": "Trace"
        }
    }
}
```

Isso registrará:

* Seleção da fábrica do cliente HTTP (pool padrão vs. conexão única)
* Configuração do handler HTTP (SocketsHttpHandler ou HttpClientHandler)
* Configurações do pool de conexões (MaxConnectionsPerServer, PooledConnectionLifetime etc.)
* Configurações de timeout (ConnectTimeout, Expect100ContinueTimeout etc.)
* Configuração de SSL/TLS
* Eventos de abertura/fechamento de conexões
* Rastreamento do ID da sessão

<h3 id="logging-debugmode">
  Modo de depuração: rastreamento de rede e diagnósticos
</h3>

Para ajudar a diagnosticar problemas de rede, a biblioteca do driver inclui um auxiliar que habilita o rastreamento de baixo nível dos componentes internos de rede do .NET. Para habilitá-lo, você deve passar uma LoggerFactory com o nível definido como Trace e definir EnableDebugMode como true (ou habilitá-lo manualmente pela classe `ClickHouse.Driver.Diagnostic.TraceHelper`). Os eventos serão registrados na categoria `ClickHouse.Driver.NetTrace`. Aviso: isso gerará logs extremamente detalhados e afetará o desempenho. Não é recomendável habilitar o modo de depuração em production.

```csharp theme={null}
var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .AddConsole()
        .SetMinimumLevel(LogLevel.Trace); // Deve estar no nível Trace para exibir eventos de rede
});

var settings = new ClickHouseClientSettings()
{
    LoggerFactory = loggerFactory,
    EnableDebugMode = true,  // Habilita o rastreamento de rede de baixo nível
};
```

<h2 id="opentelemetry">
  OpenTelemetry
</h2>

O driver oferece suporte nativo ao rastreamento distribuído com OpenTelemetry por meio da API .NET [`System.Diagnostics.Activity`](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/distributed-tracing). Quando habilitado, o driver emite spans para operações de banco de dados que podem ser exportados para backends de observabilidade, como Jaeger ou o próprio ClickHouse (por meio do [OpenTelemetry Collector](/pt-BR/guides/use-cases/observability/build-your-own/integrating-opentelemetry)).

<h3 id="opentelemetry-enabling">
  Habilitando o rastreamento
</h3>

Em aplicações ASP.NET Core, adicione o `ActivitySource` do driver do ClickHouse à configuração do OpenTelemetry:

```csharp theme={null}
builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddSource(ClickHouseDiagnosticsOptions.ActivitySourceName)  // Assina os spans do driver ClickHouse
        .AddAspNetCoreInstrumentation()
        .AddOtlpExporter());             // Ou AddJaegerExporter(), etc.
```

Para aplicativos de console, testes ou configuração manual:

```csharp theme={null}
using OpenTelemetry;
using OpenTelemetry.Trace;

var tracerProvider = Sdk.CreateTracerProviderBuilder()
    .AddSource(ClickHouseDiagnosticsOptions.ActivitySourceName)
    .AddConsoleExporter()
    .Build();
```

<h3 id="opentelemetry-attributes">
  Atributos de span
</h3>

Cada span inclui atributos de banco de dados padrão do OpenTelemetry, além de estatísticas de consulta específicas do ClickHouse que podem ser usadas para depuração.

| Atributo | Descrição |
| - | - |
| `db.system` | Sempre `"clickhouse"` |
| `db.name` | Nome do banco de dados |
| `db.user` | Nome de usuário |
| `db.statement` | Consulta SQL (se estiver habilitada) |
| `db.clickhouse.read_rows` | Linhas lidas pela consulta |
| `db.clickhouse.read_bytes` | Bytes lidos pela consulta |
| `db.clickhouse.written_rows` | Linhas gravadas pela consulta |
| `db.clickhouse.written_bytes` | Bytes gravados pela consulta |
| `db.clickhouse.elapsed_ns` | Tempo de execução no servidor em nanossegundos |

<h3 id="opentelemetry-configuration">
  Opções de configuração
</h3>

Controle o comportamento do rastreamento por meio de `ClickHouseDiagnosticsOptions`:

```csharp theme={null}
using ClickHouse.Driver.Diagnostic;

// Incluir instruções SQL nos spans (padrão: false por segurança)
ClickHouseDiagnosticsOptions.IncludeSqlInActivityTags = true;

// Truncar instruções SQL longas (padrão: 1000 caracteres)
ClickHouseDiagnosticsOptions.StatementMaxLength = 500;
```

<Warning>
  Ativar `IncludeSqlInActivityTags` pode expor dados sensíveis nos seus traces. Use com cautela em ambientes de produção.
</Warning>

<h2 id="tls-configuration">
  Configuração de TLS
</h2>

Ao se conectar ao ClickHouse via HTTPS, você pode configurar o comportamento do TLS/SSL de várias formas.

<h3 id="custom-certificate-validation">
  Validação personalizada de certificados
</h3>

Para ambientes de produção que exigem uma lógica personalizada de validação de certificados, forneça seu próprio `HttpClient` com um handler `ServerCertificateCustomValidationCallback` configurado:

```csharp theme={null}
using System.Net;
using System.Net.Security;
using ClickHouse.Driver;

var handler = new HttpClientHandler
{
    // No AutomaticDecompression needed: the driver decodes compressed responses itself.
    ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) =>
    {
        // Example: Accept a specific certificate thumbprint
        if (cert?.Thumbprint == "YOUR_EXPECTED_THUMBPRINT")
            return true;

        // Example: Accept certificates from a specific issuer
        if (cert?.Issuer.Contains("YourOrganization") == true)
            return true;

        // Default: Use standard validation
        return sslPolicyErrors == SslPolicyErrors.None;
    },
};

var httpClient = new HttpClient(handler) { Timeout = TimeSpan.FromMinutes(5) };

var settings = new ClickHouseClientSettings
{
    Host = "my.clickhouse.server",
    Protocol = "https",
    HttpClient = httpClient,
};

using var client = new ClickHouseClient(settings);
```

<Note>
  Considerações importantes ao fornecer um HttpClient personalizado

  * **Descompressão automática**: deixe `AutomaticDecompression` desativado. O próprio driver decodifica as respostas comprimidas, portanto ele não é necessário — e habilitá-lo trabalha contra você no lado da requisição: no momento do envio, o handler *também adiciona* todos os algoritmos de sua máscara ao `Accept-Encoding` de saída, ampliando o que o driver havia anunciado, de modo que o ClickHouse pode responder com um codec que você não solicitou. Consulte [Descompressão de respostas](#response-decompression).
  * **Tempo limite de inatividade**: Defina `PooledConnectionIdleTimeout` com um valor menor que o `keep_alive_timeout` do servidor (10 segundos para ClickHouse Cloud) para evitar erros de conexão causados por conexões semiabertas.
</Note>

<h2 id="performance-tuning">
  Tuning de desempenho
</h2>

Esta seção descreve como usar o client para obter o melhor desempenho possível, além das diversas opções que você pode ajustar para deixar o client mais performático no seu caso de uso específico.

<h3 id="perf-at-a-glance">
  Visão geral
</h3>

\| Se você | Faça isto |
\|---|---|---|
\| Lê linhas para POCOs | Use [`QueryAsync<T>`](#perf-read-path), e não `MapTo<T>` |
\| Faz inserções grandes | Aumente o [`InsertOptions.BatchSize`](#perf-insert-batching) |
\| Executa um aplicativo de console ou worker com muitas inserções | Ative o [Server GC](#perf-gc) |
\| Lê resultados grandes pela rede | Mantenha a compressão da resposta ativada (padrão) |
\| Insere por uma conexão rápida | Experimente [`InsertOptions.Compressor = null`](#perf-compression) |
\| Insere na mesma tabela muitas vezes | Use [`UseSchemaCache` ou `ColumnTypes`](#skip-schema-query) |
\| Lê resultados muito grandes | Aumente o [`ReadBufferSize`](#perf-buffers) |

***

<h3 id="perf-read-path">
  Leitura: escolha o caminho de materialização
</h3>

Há três maneiras de obter uma linha de um resultado, e elas não têm o mesmo custo. Alguns desses caminhos fazem boxing dos resultados, o que aumenta as alocações e reduz o desempenho.

| Como você lê | Faz boxing de cada valor | Notas |
| - | - | - |
| `QueryAsync<T>` | **No** | Lê do stream diretamente para as suas propriedades. O caminho rápido. |
| Accessors tipados do leitor (`GetInt32`, `GetInt64`, `GetDouble`, `GetGuid`, `GetDateTime`, `GetFieldValue<T>`) | **No** | Leitura sem boxing a partir de um armazenamento de valores tipados. |
| `MapTo<T>` | Sim | Materializa a linha primeiro e depois copia os valores dela. |
| `GetValue` e `GetValues` | Sim | Retornam `object`, portanto o valor precisa sofrer boxing quando você o solicita. |

Para uma leitura de 1.000.000 de linhas em 105 colunas do dataset *hits*:

| API | Alocado |
| - | -: |
| `QueryAsync<T>` | **1.372 MB** |
| `MapTo<T>` | 3.133 MB |

```csharp theme={null}
// Fast path: register the type once, then stream rows directly into it.
client.RegisterPocoType<HitRow>();

await foreach (var row in client.QueryAsync<HitRow>("SELECT * FROM hits"))
    Process(row);
```

<Note>
  *ORMs seguem o caminho rápido quando usam accessors tipados.* O linq2db registra `GetInt64`,
  `GetDouble` e `GetDateTime` para cada coluna, portanto faz a leitura sem boxing. Código que lê por meio de
  `GetValue` (incluindo um resultado `dynamic` do Dapper) aplica boxing a cada valor. Se uma consulta de ORM for muito frequente
  e ler por meio de `GetValue`, use `QueryAsync<T>` para essa consulta específica.
</Note>

***

<h3 id="perf-insert-batching">
  Inserção: tamanho do lote e paralelismo
</h3>

O tamanho do lote é o principal fator de controle sobre o throughput de inserção. `InsertOptions.BatchSize` tem
valor padrão de 100.000 linhas.

**Use lotes grandes.** Em uma inserção de 1.000.000 de linhas, aumentar de 10.000 para 100.000 linhas por
lote resultou em:

| Inserção | 10.000 linhas/lote | 100.000 linhas/lote | |
| - | -: | -: | -: |
| POCO | 15.308 ms | 7.853 ms | −49% |
| `object[]` | 17.027 ms | 10.671 ms | −37% |

Se você não puder controlar o tamanho do lote (por exemplo, quando muitos produtores pequenos enviam linhas de forma independente), use [async inserts](#async-inserts) e deixe o servidor fazer o batching.

**Uploads paralelos.** `InsertOptions.MaxDegreeOfParallelism` tem valor padrão `1`. Aumente-o para enviar
lotes simultaneamente. O ganho é maior quando a compressão está ativada, pois cada lote é comprimido
em sua própria thread. Sessions não funcionam com inserções paralelas: desative as sessions ou mantenha
`MaxDegreeOfParallelism = 1`.

**Remova o schema probe.** Cada chamada `InsertBinaryAsync` envia primeiro uma consulta `SELECT ... WHERE 1=0`
para descobrir os column types. Consulte [Ignorando a schema probe query](#skip-schema-query) para eliminar esse
round trip com `ColumnTypes` ou `UseSchemaCache`.

<Note>
  O caminho de inserção sem boxing se aplica ao format padrão `RowBinary`. O `RowBinaryWithDefaults` precisa
  examinar cada value para encontrar o marker `DBDefault`, portanto mantém o caminho mais lento.
</Note>

***

<h3 id="perf-compression">
  Compressão: as duas direções discordam
</h3>

A compressão troca CPU por bytes. Se essa troca vale a pena depende da direção da
transferência, da largura de banda da sua conexão com o servidor ClickHouse, de como seus dados interagem com o algoritmo de compressão escolhido e de você pagar ou não por cada byte transferido.

**Leituras:** mantenha a compressão ativada, a menos que o servidor esteja em execução na mesma máquina. Esse é o padrão. Em comparação com a ausência de compressão, o `zstd` no nível 1
resultou em:

| Cliente para servidor | Efeito da compressão |
| - | - |
| Mesmo host (loopback) | Custa 8% |
| Mesma região de nuvem | **Economiza 16%** |
| Uma região de distância | **Economiza 33%** |

**Inserções:** meça antes de comprimir. A economia pode não ser suficiente para justificar a ativação. Lembre-se também de que a descompressão gera carga adicional no servidor; essa carga é modesta para Zstd e LZ4, mas pode ser alta para outros algoritmos (por exemplo, Brotli).

Para desativar a compressão de inserções:

```csharp theme={null}
var options = new InsertOptions { Compressor = null };
await client.InsertBinaryAsync("my_table", columns, rows, options);
```

Para a escolha do codec, os níveis de compressão e como encontrar seu próprio ponto de equilíbrio, consulte
[Ajuste da compressão](#tuning-compression).

***

<h3 id="perf-buffers">
  Buffers
</h3>

`ReadBufferSize` define o tamanho do buffer que lê as respostas HTTP. O padrão é 64 KiB.

O driver toma emprestado esse buffer de um pool compartilhado e o devolve ao descartar o leitor, ou seja, não há uma alocação a cada consulta. Aumente esse valor para reduzir o número de recargas do buffer em resultados grandes. O driver mantém um buffer para cada leitor aberto simultaneamente, portanto o uso de memória cresce conforme o tamanho do buffer e o número de leitores concorrentes.

```csharp theme={null}
var settings = new ClickHouseClientSettings("Host=localhost") { ReadBufferSize = 256 * 1024 };
```

<Warning>
  *Sempre descarte os leitores.* Ao ser descartado, um leitor devolve seu buffer ao pool e libera sua
  conexão HTTP. Abandonar um leitor não devolve o buffer ao pool e pode deixar a conexão HTTP
  indisponível; a coleta de lixo comum não substitui o descarte explícito.
</Warning>

***

<h3 id="perf-gc">
  Runtime e GC
</h3>

**Ative o Server GC em aplicações com alto volume de inserções.** Com o mesmo código e a mesma quantidade de
bytes alocados, o Workstation GC foi até 97% mais lento nas inserções do que o Server GC.

```xml theme={null}
<PropertyGroup>
  <ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>
```

Projetos ASP.NET Core já definem isso. Aplicações de console, worker services e a maioria das imagens
de contêiner não.

A causa é o tamanho do orçamento da geração 0. O Workstation GC usa um orçamento pequeno, de modo que
os buffers de vida curta criados por um insert não morrem na geração 0. Em vez disso, eles migram para
a geração 1, o que aumenta a promoção e gera muito mais trabalho na geração 2. Em um caso de insert,
as coletas da geração 2 a cada 1.000 operações foram 4.000 com o Server GC e 73.000 com o
Workstation GC.

<Note>
  O Server GC é uma configuração de throughput, não de latência. Nas mesmas medições, o Server GC passou
  menos da metade do tempo total pausado, mas suas pausas individuais foram mais longas
  (percentil 95 de 114,6 ms contra 61,9 ms). Se o seu service for sensível a latência de cauda, meça
  os dois modes antes de escolher.
</Note>

***

<h3 id="perf-latency">
  Latência: reutilize conexões
</h3>

Estabelecer uma nova conexão TCP e realizar o handshake TLS leva um tempo considerável.
Reutilizar conexões reduz significativamente a latência das suas consultas.

* Não crie um client para cada requisição. Cada novo client, com seu próprio `HttpClient`, cria um novo
  pool de conexões e paga novamente pelo handshake. Use um único `ClickHouseClient` durante todo o ciclo de vida da aplicação. Ele é thread-safe e foi projetado para
  uso como singleton.
* Para ADO.NET e ORMs, use `ClickHouseDataSource`, de modo que todas as conexões compartilhem um único pool.

Para o conjunto completo de padrões, consulte
[Ciclo de vida e pooling de conexões](#best-practices-connection-lifetime).

***

<h3 id="perf-measuring">
  Meça você mesmo
</h3>

Em muitos casos, o desempenho dependerá do formato dos seus dados, da velocidade da sua conexão com o servidor,
de você querer ou não trocar CPU do cliente por CPU do servidor (ou vice-versa), das limitações do seu hardware, etc.
Por isso, recomenda-se medir o desempenho você mesmo, com base nos seus dados e no seu ambiente.

Para ver a parcela de trabalho do servidor, defina `QueryOptions.QueryId` e leia os counters:

```sql theme={null}
SELECT ProfileEvents['UserTimeMicroseconds'] + ProfileEvents['SystemTimeMicroseconds'] AS cpu_us,
       ProfileEvents['NetworkSendBytes'] AS sent_bytes,
       query_duration_ms
FROM system.query_log
WHERE query_id = 'your-query-id' AND type = 'QueryFinish';
```

***

<h2 id="orm-support">
  Suporte a ORMs
</h2>

ORMs exigem a API ADO.NET (`ClickHouseConnection`). Para gerenciar corretamente o ciclo de vida da conexão, crie as conexões a partir de um `ClickHouseDataSource`:

```csharp theme={null}
// Registre o DataSource como singleton
var dataSource = new ClickHouseDataSource("Host=localhost;Username=default");

// Crie conexões para uso pelo ORM
await using var connection = await dataSource.OpenConnectionAsync();
// Passe a conexão para o seu ORM...
```

<h3 id="orm-support-dapper">
  Dapper
</h3>

`ClickHouse.Driver` funciona com Dapper. O driver converte automaticamente a sintaxe `@parameter` do Dapper para a sintaxe nativa `{parameter:Type}` do ClickHouse, com os tipos inferidos a partir dos valores do .NET.

Use `ClickHouseDataSource` para gerenciar corretamente o ciclo de vida da conexão:

```csharp theme={null}
var dataSource = new ClickHouseDataSource("Host=localhost");
services.AddSingleton(dataSource); // Registrar como singleton na injeção de dependência

using var connection = dataSource.CreateConnection();
```

<h4 id="dapper-parameter-passing">
  Estilos de passagem de parâmetros
</h4>

Todos os estilos padrão de passagem de parâmetros do Dapper são compatíveis:

**Objetos anônimos:**

```csharp theme={null}
await connection.ExecuteAsync(
    "INSERT INTO users (id, name, balance) VALUES (@Id, @Name, @Balance)",
    new { Id = 1, Name = "alice", Balance = 3.14 });
```

**Classes do tipo POCO:**

```csharp theme={null}
class InsertParams
{
    public int Id { get; set; }
    public string Name { get; set; }
    public double Balance { get; set; }
}

var param = new InsertParams { Id = 42, Name = "bob", Balance = 99.9 };
await connection.ExecuteAsync(
    "INSERT INTO users (id, name, balance) VALUES (@Id, @Name, @Balance)", param);
```

**Dicionário:**

```csharp theme={null}
var parameters = new Dictionary<string, object> { { "Id", 2 } };
var rows = await connection.QueryAsync<User>(
    "SELECT id, name FROM users WHERE id = @Id", parameters);
```

**`DynamicParameters` (a partir de um dicionário ou de um objeto anônimo):**

```csharp theme={null}
var dynParams = new DynamicParameters(new { Id = 1 });
// ou: new DynamicParameters(new Dictionary<string, object> { { "Id", 1 } });

var rows = await connection.QueryAsync<User>(
    "SELECT id, name FROM users WHERE id = @Id", dynParams);
```

<h4 id="dapper-pocos">
  Consultas com POCOs
</h4>

O Dapper mapeia colunas para propriedades pelo nome (sem diferenciar maiúsculas de minúsculas):

```csharp theme={null}
class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public double Balance { get; set; }
}

// A partir de uma tabela
var users = (await connection.QueryAsync<User>("SELECT id, name, balance FROM users")).ToList();

// A partir de um literal
var row = (await connection.QueryAsync<User>("SELECT 1 as id, 'hello' as name, 2.5 as balance")).Single();
```

<h4 id="dapper-clickhouse-param-syntax">
  Sintaxe nativa de parâmetros do ClickHouse
</h4>

Quando precisar de controle explícito sobre o tipo, use diretamente no SQL a sintaxe `{param:Type}` do ClickHouse com um `Dictionary<string, object>` para os valores dos parâmetros. Não combine a sintaxe `@param` com a sintaxe `{param:Type}` para o mesmo parâmetro.

```csharp theme={null}
var parameters = new Dictionary<string, object> { { "value", 42 } };
var result = await connection.QueryAsync<int>("SELECT {value:Int32}", parameters);
```

<h4 id="dapper-where-in">
  WHERE IN
</h4>

**A expansão nativa do IN no Dapper funciona:**

```csharp theme={null}
var rows = await connection.QueryAsync<User>(
    "SELECT id, name FROM users WHERE id IN @Ids ORDER BY id",
    new { Ids = new[] { 1, 3, 5 } });
```

O Dapper reescreve isso como `WHERE id IN (@Ids1, @Ids2, @Ids3)`, e o driver converte cada parâmetro expandido.

**O `has()` do ClickHouse com parâmetro Array também funciona:**

```csharp theme={null}
var parameters = new Dictionary<string, object> { { "ids", new[] { 1, 3, 5 } } };
var rows = await connection.QueryAsync<User>(
    "SELECT id, name FROM users WHERE has({ids:Array(Int32)}, id) ORDER BY id",
    parameters);
```

<h4 id="dapper-type-handlers">
  Manipuladores de tipo personalizados
</h4>

Alguns tipos do ClickHouse, como `ITuple`, `BigInteger` e `ClickHouseDecimal`, precisam ter manipuladores registrados na inicialização:

```csharp theme={null}
// ClickHouseDecimal (para as colunas Decimal64/128/256)
SqlMapper.AddTypeHandler(new ClickHouseDecimalHandler());

// BigInteger (para as colunas Int128/Int256/UInt128/UInt256)
SqlMapper.AddTypeHandler(new BigIntegerHandler());

// IPAddress (para as colunas IPv4/IPv6)
SqlMapper.AddTypeHandler(new IpAddressHandler());
```

Consulte o [exemplo do Dapper](https://github.com/ClickHouse/clickhouse-cs/blob/main/examples/ORM/ORM_001_Dapper.cs) para ver uma implementação de exemplo de um manipulador de tipos.

<h4 id="dapper-contrib">
  Dapper.Contrib
</h4>

`GetAll<T>()` e `Get<T>(id)` funcionam. `Insert<T>()` não — ele gera sintaxe do SQL Server (`SCOPE_IDENTITY`, `[]`). Recomenda-se usar, em vez disso, o método nativo `InsertBinaryAsync` do `ClickHouseClient`.

```csharp theme={null}
[Table("test.users")]
record class UserRecord(int Id, string Name, DateTime Timestamp);

var all = await connection.GetAllAsync<UserRecord>();
var one = await connection.GetAsync<UserRecord>(1);
```

Os nomes das propriedades devem corresponder exatamente aos nomes de coluna do ClickHouse (diferenciam maiúsculas de minúsculas).

<h4 id="dapper-limitations">
  Limitações
</h4>

| O que | Status | Detalhes |
| - | - | - |
| Tuple como **resultado** | Funciona | Requer o registro de `SqlMapper.TypeHandler<ITuple>` |
| Tuple como **parâmetro** | Não suportado | O Dapper não consegue serializar `ITuple`/`Tuple<>` como valor de `DbParameter` |
| Tipos aninhados como parâmetro | Não suportado | Pelo mesmo motivo — o Dapper rejeita tipos complexos como valores de parâmetro |
| Tipos Geo como parâmetro | Não suportado | Point, Ring, Polygon, LineString, MultiLineString, MultiPolygon |
| `Dapper.Contrib.Insert<T>()` | Não suportado | Gera sintaxe específica do SQL Server |
| Tipo `Nothing` | Não suportado | Sem representação significativa no .NET |

<h3 id="orm-support-linq2db">
  Linq2db
</h3>

Este driver é compatível com o [linq2db](https://github.com/linq2db/linq2db), um ORM leve e provedor LINQ para .NET. Consulte o site do projeto para obter a documentação detalhada.

**Exemplo de uso:**

Crie uma `DataConnection` usando o provedor do ClickHouse:

```csharp theme={null}
using LinqToDB;
using LinqToDB.Data;
using LinqToDB.DataProvider.ClickHouse;

var connectionString = "Host=localhost;Port=8123;Database=default";
var options = new DataOptions()
    .UseClickHouse(connectionString, ClickHouseProvider.ClickHouseDriver);

await using var db = new DataConnection(options);
```

Os mapeamentos de tabelas podem ser definidos usando atributos ou a API fluente. Se os nomes da sua classe e propriedade corresponderem exatamente aos nomes da tabela e da coluna, nenhuma configuração será necessária:

```csharp theme={null}
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}
```

**Consultando:**

```csharp theme={null}
await using var db = new DataConnection(options);

var products = await db.GetTable<Product>()
    .Where(p => p.Price > 100)
    .OrderByDescending(p => p.Name)
    .ToListAsync();
```

**Cópia em lote:**

Use `BulkCopyAsync` para inserções em lote eficientes.

```csharp theme={null}
await using var db = new DataConnection(options);
var table = db.GetTable<Product>();

var options = new BulkCopyOptions
{
    MaxBatchSize = 100000,
    MaxDegreeOfParallelism = 1,
    WithoutSession = true
};

await table.BulkCopyAsync(options, products);
```

<h3 id="orm-support-ef-core">
  Entity Framework Core
</h3>

O provedor oficial do Entity Framework Core para ClickHouse. Mapeie classes C# para tabelas do ClickHouse, faça consultas com LINQ e insira dados via `SaveChanges` — tudo usando os padrões familiares do EF Core.

* **NuGet**: [`ClickHouse.EntityFrameworkCore`](https://www.nuget.org/packages/ClickHouse.EntityFrameworkCore)
* **Código-fonte**: [GitHub](https://github.com/ClickHouse/ClickHouse.EntityFrameworkCore)

<Note>
  Este provedor está em desenvolvimento ativo. O lançamento atual oferece suporte a consultas LINQ (incluindo junções, subconsultas e operações de conjunto), `INSERT` via `SaveChanges` / `BulkInsertAsync`, migrations com DDL completo (CREATE / ALTER / DROP) e configuração específica do motor de tabela do ClickHouse. `UPDATE` / `DELETE` não são suportados.
</Note>

<h4 id="ef-core-installation">
  Instalação
</h4>

```bash theme={null}
dotnet add package ClickHouse.EntityFrameworkCore
```

Requer o .NET 10.0 e o EF Core 10.

<h4 id="ef-core-quick-start">
  Início rápido
</h4>

Defina sua entidade e o `DbContext` e, em seguida, consulte com LINQ:

```csharp theme={null}
using Microsoft.EntityFrameworkCore;

public class PageView
{
    public long Id { get; set; }
    public string Path { get; set; }
    public DateOnly Date { get; set; }
    public string UserAgent { get; set; }
}

public class AnalyticsContext : DbContext
{
    public DbSet<PageView> PageViews { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.UseClickHouse("Host=localhost;Database=analytics");
}

// Consulta
await using var ctx = new AnalyticsContext();

var topPages = await ctx.PageViews
    .Where(v => v.Date >= new DateOnly(2024, 1, 1))
    .GroupBy(v => v.Path)
    .Select(g => new { Path = g.Key, Views = g.Count() })
    .OrderByDescending(x => x.Views)
    .Take(10)
    .ToListAsync();
```

<h4 id="ef-core-types">
  Tipos com suporte
</h4>

| Categoria | Tipos do ClickHouse | Tipos CLR |
| - | - | - |
| **Inteiros** | `Int8`–`Int64`, `UInt8`–`UInt64` | `sbyte`, `short`, `int`, `long`, `byte`, `ushort`, `uint`, `ulong` |
| **Inteiros grandes** | `Int128`, `Int256`, `UInt128`, `UInt256` | `BigInteger` |
| **Pontos flutuantes** | `Float32`, `Float64`, `BFloat16` | `float`, `double` |
| **Decimais** | `Decimal(P,S)`, `Decimal32(S)`, `Decimal64(S)`, `Decimal128(S)` | `decimal` ou `ClickHouseDecimal` |
| **Bool** | `Bool` | `bool` |
| **Strings** | `String`, `FixedString(N)` | `string` |
| **Enums** | `Enum8(...)`, `Enum16(...)` | `string` ou `enum` de C# |
| **Data/hora** | `Date`, `Date32`, `DateTime`, `DateTime64(P, 'TZ')` | `DateOnly`, `DateTime` |
| **Time** | `Time`, `Time64(N)` | `TimeSpan` |
| **UUID** | `UUID` | `Guid` |
| **Rede** | `IPv4`, `IPv6` | `IPAddress` |
| **Arrays** | `Array(T)` | `T[]`, `List<T>`, `IList<T>`, `ICollection<T>`, `IReadOnlyList<T>`, `IReadOnlyCollection<T>`, `IEnumerable<T>` |
| **Maps** | `Map(K, V)` | `Dictionary<K,V>` |
| **Tuples** | `Tuple(T1, ...)` | `Tuple<...>` ou `ValueTuple<...>` |
| **Variant** | `Variant(T1, T2, ...)` | `object` |
| **Dinâmico** | `Dynamic` | `object` |
| **JSON** | `Json` | `JsonNode` ou `string` |
| **Geoespaciais** | `Point`, `Ring`, `LineString`, `Polygon`, `MultiLineString`, `MultiPolygon`, `Geometry` | `Tuple<double,double>` e arrays correspondentes; `object` para `Geometry` |
| **Wrappers** | `Nullable(T)`, `LowCardinality(T)` | Desempacotados automaticamente |

Use `ClickHouseDecimal` (de `ClickHouse.Driver.Numerics`) em vez de `decimal` quando precisar da precisão total de colunas `Decimal128`/`Decimal256` — o `decimal` do .NET é limitado a 28–29 dígitos significativos.

<h4 id="ef-core-linq">
  Operações LINQ compatíveis
</h4>

**Consultas:** `Where`, `OrderBy`, `Take`, `Skip`, `Select`, `First`, `Single`, `Any`, `All`, `Count`, `Distinct`, `AsNoTracking`

**GROUP BY e agregações:** `GroupBy` com `Count`, `LongCount`, `Sum`, `Average`, `Min`, `Max` — incluindo `HAVING` (`.Where()` após `.GroupBy()`), várias agregações em uma única projeção e `OrderBy` com base nos resultados agregados.

**JOINs:** `Join` (INNER), padrões `GroupJoin`/`SelectMany` (LEFT e CROSS). LEFT JOIN retorna `null` de fato para linhas sem correspondência (veja [semântica de `null` em LEFT JOIN](#ef-core-join-nulls) abaixo).

**Subconsultas:** `Contains` / `IN` correlacionados, `Any` / `EXISTS`, `All` e subconsultas escalares em projeções.

**Operações de conjunto:** `Concat` (→ `UNION ALL`), `Union` (→ `UNION DISTINCT`), `Intersect`, `Except`.

**Coleções locais inline:** junções e `Contains` em coleções em memória (`int[]`, `List<T>`, etc.) são convertidos em uma série de UNIONs.

**Métodos de string:** `Contains`, `StartsWith`, `EndsWith`, `IndexOf`, `Replace`, `Substring`, `Trim`/`TrimStart`/`TrimEnd`, `ToLower`, `ToUpper`, `Length`, `IsNullOrEmpty`, `Concat` (e o operador `+`).

**Funções matemáticas:** métodos padrão de `Math` e `MathF` traduzidos para seus equivalentes no ClickHouse — funções aritméticas, logarítmicas, trigonométricas e utilitárias.

<h5 id="ef-core-join-nulls">
  Semântica de NULL em LEFT JOIN
</h5>

O provider injeta `set_join_use_nulls=1` automaticamente em cada conexão para atender às expectativas do Entity Framework em relação ao comportamento de JOIN.

Se o seu servidor ClickHouse ou profile impedir a alteração dessa configuração (por exemplo, um profile `readonly=1`), desative isso com:

```csharp theme={null}
optionsBuilder.UseClickHouse(connectionString, o => o.DisableJoinNullSemantics());
```

Com o opt-out ativado, o LEFT JOIN retorna os valores padrão das colunas do ClickHouse, e a detecção de navegação baseada em nulos do EF não funciona mais como esperado. Use comparações explícitas com `0` / `""` em vez de `== null`.

<h4 id="ef-core-insert">
  Inserção de dados
</h4>

`SaveChanges` usa a API nativa `InsertBinaryAsync` do driver — codificação RowBinary com corpo da requisição comprimido, muito mais eficiente do que SQL parametrizado:

```csharp theme={null}
await using var ctx = new AnalyticsContext();

ctx.PageViews.Add(new PageView
{
    Id = 1,
    Path = "/home",
    Date = new DateOnly(2024, 6, 15),
    UserAgent = "Mozilla/5.0"
});

await ctx.SaveChangesAsync();
```

As entidades passam de `Added` para `Unchanged` após salvar, assim como em qualquer outro provedor do EF Core.

**O tamanho do lote** é configurável (padrão: 1000):

```csharp theme={null}
optionsBuilder.UseClickHouse("Host=localhost", o => o.MaxBatchSize(5000));
```

<h4 id="ef-core-bulk-insert">
  Inserção em massa
</h4>

Para cargas de alta taxa de transferência, use `BulkInsertAsync` em vez de `SaveChanges`. Esse é um método de extensão no `DbContext` que ignora completamente o rastreador de alterações, a resolução de identidade e o gerenciamento de estado do EF Core — ele chama diretamente o `InsertBinaryAsync` do driver com codificação RowBinary e um corpo da requisição comprimido.

Isso o torna ideal para carregar grandes conjuntos de dados quando você não precisa rastrear entidades após a inserção:

```csharp theme={null}
var events = Enumerable.Range(0, 100_000)
    .Select(i => new PageView
    {
        Id = i,
        Path = $"/page/{i}",
        Date = DateOnly.FromDateTime(DateTime.Today)
    });

long rowsInserted = await ctx.BulkInsertAsync(events);
```

A entrada pode ser qualquer `IEnumerable<T>` — ela processa as entidades em fluxo, sem carregá-las todas na memória. O valor retornado é o número de linhas inseridas. As entidades **não** ficam vinculadas ao `DbContext` após a inserção, portanto não há transição de estado de `Added` → `Unchanged`.

<h4 id="ef-core-enums">
  Enums
</h4>

As colunas `Enum8`/`Enum16` do ClickHouse podem ser mapeadas como propriedades `string` ou como tipos `enum` em C#. Ao usar enums em C#, o provedor converte automaticamente entre o enum e sua representação textual:

```csharp theme={null}
public enum Status { Active, Inactive, Pending }

public class User
{
    public long Id { get; set; }
    public Status Status { get; set; }
}

// Consulta com valores do enum
var active = await ctx.Users
    .Where(u => u.Status == Status.Active)
    .ToListAsync();
```

<h4 id="ef-core-value-converters">
  Conversões de tipos personalizadas
</h4>

O sistema `ValueConverter` do EF Core permite mapear tipos personalizados para tipos que o provedor já suporta. O provedor nunca vê seu tipo personalizado — o EF Core faz a conversão na interface entre os dois.

**Conversão por propriedade:**

```csharp theme={null}
public class Money
{
    public decimal Amount { get; set; }
    public string Currency { get; set; }
}

public class Order
{
    public long Id { get; set; }
    public Money Price { get; set; }
}

// No OnModelCreating:
modelBuilder.Entity<Order>()
    .Property(o => o.Price)
    .HasConversion(
        m => $"{m.Amount}|{m.Currency}",
        s => new Money
        {
            Amount = decimal.Parse(s.Split('|')[0]),
            Currency = s.Split('|')[1]
        })
    .HasColumnType("String");
```

**Classe de conversor reutilizável:**

```csharp theme={null}
public class MoneyConverter : ValueConverter<Money, string>
{
    public MoneyConverter() : base(
        m => $"{m.Amount}|{m.Currency}",
        s => Parse(s)) { }

    private static Money Parse(string s)
    {
        var parts = s.Split('|');
        return new Money { Amount = decimal.Parse(parts[0]), Currency = parts[1] };
    }
}

// Aplicar a uma única propriedade:
.HasConversion<MoneyConverter>()

// Ou a todas as propriedades de um tipo por convenção:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Properties<Money>()
        .HaveConversion<MoneyConverter>();
}
```

<h4 id="ef-core-column-types">
  Anotações de tipo de coluna
</h4>

Para tipos escalares como `string`, `int`, `DateTime` etc., o provedor deduz automaticamente o tipo do ClickHouse. Para tipos parametrizados e wrappers, é necessário especificar explicitamente o tipo do ClickHouse.

**Usando anotações de dados (atributos):**

```csharp theme={null}
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;

[Table("sensor_readings")]
public class SensorReading
{
    public long Id { get; set; }

    [Column(TypeName = "Array(String)")]
    public string[] Tags { get; set; }

    [Column(TypeName = "Map(String, String)")]
    public Dictionary<string, string> Metadata { get; set; }

    [Column(TypeName = "Nullable(Float64)")]
    public double? Value { get; set; }

    [Column(TypeName = "Decimal128(18)")]
    public decimal HighPrecision { get; set; }
}
```

**Usando a API fluente no `OnModelCreating`:**

```csharp theme={null}
modelBuilder.Entity<SensorReading>(e =>
{
    e.ToTable("sensor_readings");
    e.Property(x => x.Tags).HasColumnType("Array(String)");
    e.Property(x => x.Metadata).HasColumnType("Map(String, String)");
    e.Property(x => x.Value).HasColumnType("Nullable(Float64)");
    e.Property(x => x.Category).HasColumnType("LowCardinality(String)");
    e.Property(x => x.HighPrecision).HasColumnType("Decimal128(18)");
});
```

Wrappers aninhados como `Array(Nullable(Int32))` e `LowCardinality(Nullable(String))` são suportados — o provedor desempacota `Nullable` e `LowCardinality` automaticamente em todos os níveis de aninhamento.

<h4 id="ef-core-variant-dynamic">
  Colunas Variant e Dynamic
</h4>

As colunas `Variant(T1, T2, ...)` e `Dynamic` do ClickHouse são mapeadas para `object` no .NET. Como `object` é genérico demais para a inferência automática de tipos, você deve declarar explicitamente o tipo de armazenamento por meio de `.HasColumnType()`:

```csharp theme={null}
public class Event
{
    public long Id { get; set; }
    public object? Payload { get; set; }
}

// No OnModelCreating:
entity.Property(e => e.Payload).HasColumnType("Variant(String, UInt64, Array(UInt64))");
// ou:
entity.Property(e => e.Payload).HasColumnType("Dynamic");
```

Ao ler, o valor é desserializado automaticamente para o tipo .NET correspondente ao discriminador armazenado (por exemplo, `string`, `ulong`, `ulong[]`).

<h4 id="ef-core-json">
  Colunas JSON
</h4>

O provedor dá suporte ao tipo de coluna `Json` do ClickHouse, com mapeamento para `System.Text.Json.Nodes.JsonNode` (principal) ou `string` (via `ValueConverter` automático):

```csharp theme={null}
using System.Text.Json.Nodes;

public class Event
{
    public long Id { get; set; }
    public JsonNode? Data { get; set; }
}

// No OnModelCreating:
entity.Property(e => e.Data).HasColumnType("Json");
```

A leitura e a escrita de JSON funcionam tanto com `SaveChanges` quanto com `BulkInsertAsync`:

```csharp theme={null}
ctx.Events.Add(new Event
{
    Id = 1,
    Data = JsonNode.Parse("""{"action": "click", "x": 100, "y": 200}""")
});
await ctx.SaveChangesAsync();

var ev = await ctx.Events.Where(e => e.Id == 1).SingleAsync();
string action = ev.Data!["action"]!.GetValue<string>(); // "click"
```

Se você preferir strings JSON brutas, mapeie a propriedade como `string`, com o tipo de coluna `Json` — o provedor aplica um `ValueConverter` automaticamente:

```csharp theme={null}
public class Event
{
    public long Id { get; set; }
    public string? Data { get; set; }  // JSON bruto como string
}

entity.Property(e => e.Data).HasColumnType("Json");
```

<Note>
  * **Sem tradução de caminhos JSON** — `entity.Data["name"]` no LINQ não é convertido para a sintaxe SQL `data.name` do ClickHouse. Filtre colunas não JSON e inspecione o JSON na memória.
  * **Semântica de NULL** — o tipo JSON do ClickHouse retorna `{}` (objeto vazio) para valores NULL, em vez de SQL NULL.
  * **Precisão de inteiros** — o JSON do ClickHouse armazena todos os inteiros como `Int64`. Ao ler com `JsonNode`, use `GetValue<long>()` em vez de `GetValue<int>()`.
</Note>

<h4 id="ef-core-engines">
  Motores de tabela
</h4>

Configure os motores de tabela do ClickHouse e as cláusulas específicas de cada motor por meio da API fluente `ToTable(name, t => ...)`. Quando nenhum motor é configurado, o provedor usa `MergeTree`, com `ORDER BY` derivado da chave primária da entidade.

```csharp theme={null}
modelBuilder.Entity<Event>(e =>
{
    e.ToTable("events", t => t
        .HasMergeTreeEngine()
        .WithOrderBy("UserId", "Timestamp")
        .WithPartitionBy("toYYYYMM(Timestamp)")
        .WithPrimaryKey("UserId")
        .WithSettings("index_granularity = 8192"));
});
```

Famílias de motores compatíveis:

| Motor | Método fluente | Observações |
| - | - | - |
| `MergeTree` | `HasMergeTreeEngine()` | Padrão quando nada é configurado |
| `ReplacingMergeTree` | `HasReplacingMergeTreeEngine("Version", "IsDeleted")` ou `HasReplacingMergeTreeEngine<T>(e => e.Version)` | Colunas Version / IsDeleted opcionais |
| `SummingMergeTree` | `HasSummingMergeTreeEngine(…)` ou `HasSummingMergeTreeEngine<T>(e => new { … })` | Colunas a somar opcionais |
| `AggregatingMergeTree` | `HasAggregatingMergeTreeEngine()` | — |
| `CollapsingMergeTree` | `HasCollapsingMergeTreeEngine("Sign")` ou `HasCollapsingMergeTreeEngine<T>(e => e.Sign)` | A coluna `Sign` deve ser `Int8` |
| `VersionedCollapsingMergeTree` | `HasVersionedCollapsingMergeTreeEngine("Sign", "Version")` ou `<T>(e => e.Sign, e => e.Version)` | — |
| `GraphiteMergeTree` | `HasGraphiteMergeTreeEngine("config_section")` | — |
| `Log`, `TinyLog`, `StripeLog`, `Memory` | `HasLogEngine()`, `HasTinyLogEngine()`, `HasStripeLogEngine()`, `HasMemoryEngine()` | Sem ORDER BY / PARTITION BY |

**Cláusulas do motor:** `WithOrderBy`, `WithPartitionBy`, `WithPrimaryKey`, `WithSampleBy`, `WithTtl`, `WithSettings`. Todas são anexadas ao construtor de motor retornado por `HasXxxEngine()`.

**Recursos em nível de coluna:** `HasCodec`, `HasTtl`, `HasComment`, `HasDefault` — todos participam das migrações.

**Índices de data skipping** — via `HasIndex(...).HasSkippingIndexType(...)`:

```csharp theme={null}
modelBuilder.Entity<Event>()
    .HasIndex(e => e.UserId)
    .HasSkippingIndexType("minmax")
    .HasGranularity(4);

// Índice com parâmetros (ex.: bloom_filter, tokenbf_v1):
modelBuilder.Entity<Event>()
    .HasIndex(e => e.Tag)
    .HasSkippingIndexType("bloom_filter")
    .HasSkippingIndexParams("0.01")
    .HasGranularity(1);
```

Índices padrão (sem skipping) são ignorados silenciosamente, já que não têm equivalente no ClickHouse. Índices únicos geram exceção, pois o ClickHouse não impõe unicidade.

<h4 id="ef-core-migrations">
  Migrações
</h4>

Fluxo de trabalho padrão das migrações do EF Core:

```bash theme={null}
dotnet ef migrations add InitialCreate
dotnet ef database update
```

Operações suportadas:

| Operação | Emite |
| - | - |
| `CREATE TABLE` | Inclui cláusula de motor, ORDER BY, PARTITION BY, SETTINGS, codecs/TTL/comentários/valores padrão de coluna |
| `ALTER TABLE ADD COLUMN` | — |
| `ALTER TABLE DROP COLUMN` | — |
| `ALTER TABLE MODIFY COLUMN` | Lida com alteração de tipo e adição/remoção de anotações (CODEC, TTL, COMMENT, DEFAULT) |
| `ALTER TABLE RENAME COLUMN` | — |
| `RENAME TABLE` | — |
| `ALTER TABLE ADD INDEX` / `DROP INDEX` | Somente índices de data skipping |
| `CREATE DATABASE` / `DROP DATABASE` | Via `EnsureCreated` / `EnsureDeleted` e migrações |

<h4 id="ef-core-limitations">
  Limitações de migração
</h4>

| Recurso | Motivo |
| - | - |
| Chaves estrangeiras | O ClickHouse não aplica chaves estrangeiras. As migrações rejeitam `AddForeignKey`; o validador do modelo emite um aviso na compilação do modelo. |
| Restrições de unicidade / índices únicos | O ClickHouse não aplica unicidade. Índices únicos geram erro no momento da migração. |
| Valores gerados pelo servidor (auto-incremento / `IDENTITY`) | O ClickHouse não tem equivalente. |
| colunas `Nested(…)` | Ainda não há suporte como tipo CLR mapeado. |
| Entidades de propriedade como JSON (`.ToJson()`) | O mapeamento estrutural de JSON para entidades de propriedade ainda não foi implementado. Em vez disso, use `JsonNode` / `string` em uma coluna `Json` (consulte [colunas JSON](#ef-core-json)). |

Além das migrações, o provedor também ainda não oferece suporte a:

* **`UPDATE` / `DELETE`**
* **Transações**: `BeginTransaction` é um no-op. Não há suporte a transações ACID no ClickHouse.
* **Tradução de consultas com caminho JSON**: `entity.Data["key"]` em LINQ não é traduzido para a sintaxe SQL `data.key` do ClickHouse. Aplique filtros em colunas não JSON e inspecione o JSON na memória.

<h2 id="limitations">
  Limitações
</h2>

<h3 id="valuetuple-caveat">
  Tuple com 8+ elementos e uma tupla aninhada na última posição
</h3>

Tipos `ValueTuple` de C# com mais de 7 elementos usam um esquema de aninhamento gerado pelo compilador: o 8º argumento genérico (`TRest`) é, ele próprio, um `ValueTuple` que contém os elementos restantes. Por exemplo, `(int, int, int, int, int, int, int, string, string)` é compilado como `ValueTuple<int, int, int, int, int, int, int, ValueTuple<string, string>>`.

Isso cria uma ambiguidade quando a coluna do ClickHouse é uma tupla de 8 elementos em que o último elemento também é uma tupla — por exemplo, `Tuple(Int32, Int32, Int32, Int32, Int32, Int32, Int32, Tuple(String, String))`. O driver não consegue distinguir entre:

* Uma **tupla plana de 9 elementos** (aninhamento TRest gerado pelo compilador)
* Uma **tupla de 8 elementos** em que o último elemento é um `Tuple(String, String)` aninhado

Ambos produzem o mesmo tipo .NET: `ValueTuple<int, int, int, int, int, int, int, ValueTuple<string, string>>`.

O driver trata o 8º argumento como TRest (ou seja, o expande), o que significa que o caso de 8 elementos com tupla aninhada será serializado incorretamente.

Isso afeta tanto `System.Tuple` quanto `ValueTuple`, já que ambos usam aninhamento TRest para >7 elementos. Tuple com 7 ou menos elementos, ou Tuple em que o último elemento não é uma tupla, não são afetadas.

**Solução alternativa:** Envolva a tupla interna em uma camada extra para que o driver consiga distingui-la do aninhamento TRest:

```csharp theme={null}
// Instead of this (ambiguous — is it 8 elements or 9 flat?):
Tuple.Create(1, 2, 3, 4, 5, 6, 7, Tuple.Create("a", "b"))

// Do this (unambiguous — inner tuple is wrapped):
Tuple.Create(1, 2, 3, 4, 5, 6, 7, Tuple.Create(Tuple.Create("a", "b")))
```

***

<h3 id="aggregatefunction-columns">
  Colunas do tipo AggregateFunction
</h3>

Colunas do tipo `AggregateFunction(...)` não podem ser consultadas nem inseridas diretamente.

Para inserir:

```sql theme={null}
INSERT INTO t VALUES (uniqState(1));
```

Para selecionar:

```sql theme={null}
SELECT uniqMerge(c) FROM t;
```

***
