Skip to main content

Experimental status

The native TCP client API is experimental. Its public surface may change in a future release.
The main client, data source, session, operations interfaces, and dependency-injection extensions produce compiler warning CHTCP0001. To acknowledge the warning while evaluating the API, add it to NoWarn in your project file:
Suppress the warning only after accepting that code using the API may need changes in a future release.

Supported .NET versions

The native TCP client supports the following .NET versions:
  • .NET 8.0
  • .NET 9.0
  • .NET 10.0

Supported ClickHouse versions

The TCP client officially supports the last 2 LTS versions, as well as the last 3 releases of the ClickHouse server.

Installation

The native TCP client is included in the ClickHouse.Driver package. Install it from NuGet:
Or using the NuGet Package Manager:

Quick start

Migrating from the HTTP client

The TCP API is separate from IClickHouseClient; it is not a transport option for ClickHouseClient or ClickHouseConnection. Most SQL can be reused, but construct a ClickHouseTcpClient and migrate each operation to its TCP equivalent.

Method equivalents

For example, an HTTP reader loop:
becomes an asynchronous TCP row stream:

Options and parameters

HTTP QueryOptions and ClickHouseParameterCollection are not interchangeable with their TCP counterparts. Move the values into ClickHouseTcpQueryOptions, where settings are strings and parameters are part of the options object:
The TCP client supports ClickHouse-native {name:Type} placeholders but does not rewrite ADO.NET-style @name placeholders. It also has no per-query database, role, bearer token, custom header, parameter resolver or formatter, or read converter. See limitations for the features that remain HTTP-only.

Connection and result lifetime

Both high-level clients are thread-safe and should be reused. The TCP client owns persistent native connections rather than HTTP connections, implements IAsyncDisposable, and should normally be disposed asynchronously when the application shuts down. QueryAsync rows are caller-owned. StreamAsync blocks and their columns are borrowed and valid only for the current iteration; copy any values that must outlive it. Always finish or dispose an enumeration so its connection returns to the pool. Use OpenSessionAsync when several operations must run on the same connection and retain session state. See the complete HTTP-to-TCP migration example for both clients used against the same table.

Configuration

There are two ways to configure a native TCP client:
  • Connection string: Semicolon-separated key/value pairs that specify the server endpoint, authentication credentials, and other connection options.
  • ClickHouseTcpClientOptions object: A strongly typed, immutable configuration object set in code.
Pass either form to the ClickHouseTcpClient constructor. Use ClickHouseTcpConnectionStringBuilder to parse or build a connection string and convert it to ClickHouseTcpClientOptions.
The following sections list the available settings, their defaults, and their effects.

Connection settings

Connection-string values for TimeSpan properties are expressed in seconds.

Data format and serialization

Connection pooling

One connection runs one operation at a time. The pool lets operations run concurrently and reuses connections between them.

Security

Without TLS, the password and all query data travel over the network without encryption. Keep TlsAllowInvalidCertificates set to false outside local development.
The secure native protocol is a separate server endpoint rather than an in-band upgrade. A TLS client must connect to a secure native port; it does not fall back to plaintext. ConfigureTls is applied last and can override certificate and hostname validation, so review custom callbacks carefully.

Logging and debugging

The client logger records client-side lifecycle and operation events. Server log packets are sent to ClickHouseTcpQueryCallbacks.OnLog instead. Statement text can contain sensitive data, so it is excluded from activity tags by default and heavily truncated in debug logs.

Custom settings

ClickHouseTcpClientOptions.CustomSettings applies ClickHouse server settings to every query and insert. Per-operation ClickHouseTcpQueryOptions.Settings values override client-level values with the same name.
In a connection string, prefix each server setting with set_:
Do not include the set_ prefix when adding a setting to CustomSettings or per-operation Settings.

ClickHouseTcpQueryOptions

ClickHouseTcpQueryOptions supplies per-operation settings. All properties are optional.

ClickHouseTcpInsertOptions

ClickHouseTcpInsertOptions extends ClickHouseTcpQueryOptions with settings for native block inserts. All query-option properties are also available.

ClickHouseTcpClient

ClickHouseTcpClient is the high-level API for the native TCP protocol. It executes statements and scalar queries, streams results as columnar blocks, rows, or POCOs, inserts columnar or row data, and opens pinned sessions. The client is thread-safe and owns a connection pool. Create one client per endpoint, share it across the application, and dispose it at shutdown. Operations run concurrently up to MaxPoolSize; additional operations wait for a connection for up to PoolTimeout.

Creating a client

Create a client from a connection string or ClickHouseTcpClientOptions. See Configuration for the available settings. Using a connection string:
Using strongly typed options:
Prefer asynchronous disposal. Disposing the client closes idle connections and waits up to PoolTimeout for active operations before aborting them.

Dependency injection

AddClickHouseTcpDataSource registers one singleton data source and exposes its shared client as IClickHouseTcpClient and IClickHouseTcpOperations:
The service provider owns the data source and connection pool. Do not dispose an injected client; dispose the provider at application shutdown. When the options do not specify a LoggerFactory, the registration uses the container’s ILoggerFactory. A serviceKey can register multiple independent data sources.

Executing queries

Use ExecuteAsync for statements that do not return rows, such as DDL and mutations:
The method completes after the server acknowledges the statement. If a statement returns rows, ExecuteAsync reads and discards them. For INSERT INTO ... VALUES, use InsertAsync or InsertRowsAsync and end the SQL statement at VALUES without inline values.

Reading data

Choose the read API by the shape the application needs:

Scalar reads

ExecuteScalarAsync returns the first column of the first row as an object:
The method reads the complete response and discards values after the first. Write a query that returns one row rather than using it to stop a large result early. A NULL first value and a query that returns no rows both produce null.

Row reads

QueryAsync streams each row as an owned object[], with values in result-column order:
The row array remains valid after enumeration advances. This tier is convenient when no result model exists, but it allocates one array per row and boxes value types.

POCO reads

QueryAsync<T> maps columns to public settable properties. Names match without regard to case or underscores; use ClickHouseTcpColumn to set an explicit name and ClickHouseTcpNotMapped to exclude a property.
POCO mapping performs conversions and allocates one object per row.

Columnar block reads

StreamAsync exposes the native columnar result as a sequence of Block values. This is the fastest way to read data.
Blocks are borrowed. A block, its columns, and their value spans are valid only for the current iteration. Do not dispose or retain them; any data that needs to outlive the loop body must be explicitly copied into memory that you own.
Always finish enumeration or dispose the enumerator so its connection returns to the pool.
Block.Column<T> accesses the CLR type produced by the decoder. Use Block.ReadAs<T> or a specialized interface such as IDateTimeColumn when conversion is required.

Inserting data

Native inserts send data as columnar wire blocks. The SQL must be an INSERT INTO ... VALUES statement with no inline value list. Use typed columns for the least client-side work, or row and POCO overloads when the application already holds row-oriented data.

Columnar inserts

InsertAsync accepts data already grouped into typed columns. This is the fastest way to insert data.
Columns are matched to the SQL column list by name, not argument order, and must have equal row counts. The server supplies defaults for table columns omitted from the statement. Do not modify caller-owned arrays until the insert completes. This API avoids the row-to-column projection and value-type boxing required by row inserts. A column read from a borrowed result block can also be passed directly to InsertAsync, provided the insert completes before that block is released.

Row inserts

InsertRowsAsync accepts object[] rows. Values match the SQL column list by position:
Rows are converted to columns one block at a time. Each row must contain one value for every column named in the statement, and values in one column must use a consistent CLR type except where the target is Variant or Dynamic. Do not modify the rows until the operation completes.

POCO inserts

The generic InsertRowsAsync<T> overload maps target columns to public readable properties using the same naming and attribute rules as POCO reads:
Every column named in the statement must map to a compatible public getter. POCO rows are converted one wire block at a time, so an invalid value in a later block can be found after earlier blocks have already been sent. ClickHouseTcpNotMapped keeps application-only properties such as ImportBatch out of the mapping.

SQL parameters

The native client binds values to ClickHouse-native {name:Type} placeholders. It does not rewrite ADO.NET-style @name placeholders.
Each parameter needs a type from its SQL placeholder or its ClickHouseTcpParameter.ClickHouseType property. Use the Identifier type for table and column names. When a DateTime or DateTimeOffset represents an instant, include a timezone such as {timestamp:DateTime('UTC')}; otherwise the server session timezone could change its meaning.

Query ID

Every operation has a query ID used by ClickHouse system tables, client logs, and trace spans. When QueryId is null or empty, the client generates a new GUID. Set it when the operation must correlate with an application identifier:

Query progress and metadata

Set ClickHouseTcpQueryOptions.Callbacks to receive metadata interleaved with the response:
Progress values are increments rather than running totals. Callbacks run synchronously on the thread reading the response, in packet order; keep them fast and do not throw. Blocks supplied to OnLog, OnProfileEvents, OnTotals, and OnExtremes are borrowed and must not escape the callback. ClickHouse does not send query-progress packets for rows uploaded by an insert. Use OnBlockWritten to observe blocks sent by the client; it reports transport progress, not that the server committed the insert.

Cancellation

Every operation accepts a CancellationToken:
Cancellation or abandoning a streamed result makes its connection unsafe to reuse, so the client closes that connection instead of returning it to the pool. The client remains usable and opens or reuses another connection for later operations. On a pinned session, cancellation can therefore end the session and lose its temporary tables and settings.

Server information and health checks

GetServerInfoAsync returns server identity, version, timezone, and negotiated protocol information from the native handshake:
Gate wire-level features on ProtocolRevision, which is the revision negotiated by the client and server. Gate SQL features such as data types and functions on Version. The method uses an existing connection when one is available and opens one when the pool is empty. Use PingAsync for a readiness check over the native protocol:
A successful ping verifies native connectivity and authentication. It does not verify access to an application table.

More examples

For complete runnable examples covering reads, inserts, types, sessions, TLS, observability, and error handling, see the native TCP examples in the GitHub repository.

Sessions

Ordinary client operations rent any available pooled connection, so connection-local state is neither isolated nor guaranteed to persist between calls. Different connections in the pool can connect to different servers. Open a session when temporary tables, SET statements, or another sequence of operations must use one connection.
IClickHouseTcpSession exposes the same query, execution, insert, ping, and server-information APIs as the client. The session is not thread safe. Its operations are sequential: starting another operation before the current one finishes is rejected. A streamed result keeps the session busy until it is fully enumerated or its enumerator is disposed. Each session occupies one MaxPoolSize slot for its lifetime. The client can continue using other pool connections, but when the pool contains only one connection, other client operations wait until the session is disposed or PoolTimeout expires. Dispose the session before disposing its parent client. Session disposal closes the pinned connection rather than returning it to the pool, preventing later callers from inheriting its temporary tables or settings. A connection or protocol failure, cancellation, or an incomplete result stream can make the session unusable; when IsOpen becomes false, open a new session.

Best practices

Client lifetime and pooling

Create one ClickHouseTcpClient or ClickHouseTcpDataSource for each distinct endpoint and configuration, and reuse it for the lifetime of the application. The client is thread-safe and owns its connection pool; creating one per operation creates a new pool each time and prevents connection reuse. In a dependency-injection application, register AddClickHouseTcpDataSource once and inject IClickHouseTcpClient. The service provider owns the client, so consumers must not dispose the injected instance. Outside DI, dispose the client at application shutdown with await using. Each native connection carries one operation at a time. MaxPoolSize therefore limits both open connections and concurrent operations; additional callers wait for a slot for up to PoolTimeout. Choose a value that supports the application’s real concurrency without exceeding server-side connection and query limits. Raising it does not make one query faster. Keep these pool consumers in mind:
  • A streamed result holds its connection until its enumerator is disposed.
  • A session holds one connection for the session’s entire lifetime and accepts one operation at a time.
  • Abandoning a result, in-flight cancellation, or a protocol/connection failure causes the connection to be closed rather than reused.
Dispose sessions promptly and always enumerate async results to completion or dispose their enumerator. Use the ClickHouse.Driver.Tcp.Pool logging category to diagnose pool exhaustion, unexpected connection retirement, or frequent redials.

Timeouts and cancellation

The TCP client has separate limits for separate waits: A checkout that first waits for a pool slot and then opens a connection can take up to PoolTimeout + DialTimeout. ReadTimeout restarts for each transport read, so a long query that continues sending progress or data can run longer than that value. Set it high enough for legitimate periods of server silence; set it to TimeSpan.Zero only when another timeout reliably bounds the operation. Use a cancellation token when you need an end-to-end timeout:
The client attempts to send a native Cancel packet when it can do so safely, then closes a connection whose response was not fully drained. Later operations on the pooled client remain usable, but cancellation can make a pinned session unusable. A cancelled or timed-out insert can have an unknown outcome; follow the insert retry guidance rather than blindly replaying it.

Date and time handling

Prefer DateTime('UTC') or DateTime64(S, 'UTC') for timestamps and use DateTimeOffset or a DateTime whose Kind is Utc in application code. This makes the represented instant explicit and avoids dependence on the server or session timezone. The TCP client applies these rules when writing DateTime values: An unspecified time skipped by a daylight-saving transition is rejected because it names no instant. An ambiguous fall-back time selects the earlier occurrence. Use DateTimeOffset when the choice must be explicit. For SQL parameters that represent an instant, include the timezone in the placeholder:
On columnar reads, DateTime and DateTime64 expose their raw uint or long counts by default. Use Block.ReadAs<DateTimeOffset>, IDateTimeColumn.GetDateTimeOffset, or a matching POCO property when a calendar value is more convenient. Keep the raw count when every digit matters: DateTimeOffset has 100-nanosecond resolution, so reading DateTime64 at scale 8 or 9 through it loses sub-tick precision. Use DateOnly for Date and Date32. Use TimeSpan for Time and Time64 values that may be negative or exceed 24 hours; TimeOnly is appropriate only when the column is constrained to a time of day.

Streaming and result lifetime

Use StreamAsync and typed columns for high-throughput processing. This follows ClickHouse’s columnar layout and avoids per-row arrays, boxing, and POCO allocation. Use QueryAsync<T> when an owned application model is more important than the lowest allocation rate, and use the untyped QueryAsync only when the schema is not known at compile time. Blocks returned by StreamAsync are borrowed. The block, its columns, composite child columns, and all spans are valid only for the current loop iteration. Process them in place or copy values that must outlive the iteration:
Do not dispose a yielded block yourself. Let await foreach dispose the enumerator. If only part of a result is needed, express that in SQL with LIMIT and select only the required columns. Breaking out early cancels the remaining response and prevents that connection from being reused.

Insert shape and batching

Prefer InsertAsync with typed columns when data is already columnar. It performs the least client-side conversion. InsertRowsAsync<T> is the natural choice for POCO data; untyped object[] rows are the most flexible but allocate arrays and box value types. Send a useful number of rows in each insert call rather than issuing many tiny inserts. Within one call, MaxRowsPerBlock splits the data into native wire blocks. Its default of 50,000 rows also bounds the temporary column buffers used when converting row and POCO inputs. Increasing it can improve throughput for narrow rows but increases peak memory; setting it to null writes the whole insert as one block and converts all row-oriented input for that block together. MaxSendBufferBytes independently limits how much encoded data is buffered before it is flushed to the socket. It is a soft cap checked between columns, so one wide column can exceed it. Row and POCO inputs are converted a block at a time. A bad value in a later block can therefore be found after earlier blocks have already been sent. Validate nullability and CLR types before the insert when data quality is uncertain, and set a data-derived DeduplicationToken whenever the logical batch may be retried.

Supported data types

The tables below distinguish the type returned by the decoded IColumn<T> from additional types available through conversion via Block.ReadAs<T>, QueryAsync<T> POCO mapping, or inserts. You can ask the same resolver used by those operations whether a particular mapping is supported:
Mappings for Nullable, Array, Tuple, Map, and LowCardinality apply recursively to their inner types.

Type mapping: reading from ClickHouse

Default .NET type is the T exposed by Block.Column<T> and IColumn<T>. Types in the Also readable as column are available through Block.ReadAs<T> and POCO properties.

Integer types

Floating point types

Decimal types

ClickHouseTcpDecimal stores an arbitrary-size integer mantissa and a scale, preserving values whose precision exceeds the range of decimal.

Boolean type

String types

Use IStringColumn.GetBytes(row) or read as byte[] when the data may not be valid UTF-8. FixedString(N) preserves all N bytes, including trailing zero bytes.

Date and time types

The raw integer representation is the default so columnar reads retain the exact wire value. The calendar conversions use the timezone declared by the type, or the session timezone when the type does not declare one. Reading Time or Time64 as TimeOnly throws when a value is negative or at least 24 hours; use TimeSpan for the full ClickHouse range.

Enum types

IEnumColumn also exposes the declared label-to-ordinal mapping.

Other scalar types

Composite types

Tuples can contain up to seven elements. A Nested column is arity-independent and is best read through INestedColumn, which exposes its named flat field columns and shared row offsets without allocating boxed records. With ClickHouse’s default flatten_nested = 1, nested fields arrive as separate dotted Array(T) columns instead.

Variant, Dynamic, and JSON types

Variant and Dynamic return the CLR value for the selected type in each row, or null. Their columnar views expose the discriminator stream and one typed child column per runtime type, avoiding boxing when processing data in columns. The TCP client reads JSON through ClickHouse’s String serialization. The client enables output_format_native_write_json_as_string = 1 by default; disabling that setting makes JSON reads unsupported. ClickHouse parses and normalizes JSON, so the returned text may differ in key order, whitespace, and number formatting from the inserted text.

Geometry types

Geometry is exposed as an IVariantColumn over the six geometry types.

QBit type

The TCP client supports the two-argument, unstrided QBit(T, N) form. The three-argument strided layout is not supported.

AggregateFunction types

An AggregateFunction column contains a function-specific intermediate state. Finalize it on the server, for example with sumMerge(column), and read the resulting value instead.

Type mapping: writing to ClickHouse

Each entry is the CLR type of one row in the IColumn passed to InsertAsync. These mappings are exact: the TCP client does not apply Convert-style coercions.

Integer types

Floating point types

Writing BFloat16 truncates each float to the 16-bit brain floating-point representation.

Decimal types

The write throws OverflowException when the scaled mantissa exceeds the declared precision.

Boolean type

String types

FixedString(N) deliberately does not accept string; encode and pad the value explicitly so its exact binary representation is unambiguous.

Date and time types

Raw integers use the units shown in the reading table. DateTimeOffset preserves the instant. DateTime respects its Kind; an Unspecified value is interpreted as wall-clock time in the column timezone, or in the session timezone when the column does not declare one.

Enum types

An unknown label is rejected before data is sent.

Other scalar types

Composite types

The accepted shape is recursive, so Array(Nullable(DateTime)) accepts a DateTime?[] for each row and Map(String, UInt32) accepts a KeyValuePair<string, uint>[]. Direct Nested writes use the dense column shape returned by a previous read. For application-built inserts, keep the default flatten_nested = 1 representation and write each dotted Array(T) field as its own column.

Variant, Dynamic, and JSON types

For Variant, the runtime type must uniquely match an alternative’s default CLR type. Convenience mappings such as writing a DateTime to a DateTime alternative are not used inside a variant; use the alternative’s default raw type instead. Dynamic supports scalar and recursively supported array, map, and tuple values.

Geometry types

For an application-built Geometry column, only Point and MultiPolygon have unique CLR shapes. Ring and LineString share a shape, as do Polygon and MultiLineString, so the client cannot infer which alternative an object value intends. A decoded Geometry column retains its discriminators and can be inserted again without that ambiguity.

QBit type

AggregateFunction types

Compression

The client enables compression by default and uses LZ4 for blocks it writes; ClickHouse also defaults to LZ4 for response blocks. Compression applies to result blocks and blocks uploaded by an insert, while query text, progress, exceptions, and other protocol packets keep their normal wire encoding. Use the Compression connection-string key to select the codec used by the client:
The equivalent strongly typed configuration uses IClickHouseCompressor:
Set Compressor = null to disable compression. A custom compressor must support native block framing; compressors that only support HTTP bodies are rejected when the client is created.

Selecting the response codec

Compressor chooses how blocks written by the client are compressed. The native query packet asks the server for compression but does not name a codec, so ClickHouse selects the response codec from its network_compression_method setting. LZ4 is the server default. Set the response codec per operation when needed:
LZ4 generally uses less CPU and is the best default on fast networks. ZSTD generally produces smaller frames at a slightly higher CPU cost. Disabling compression can help for a server on the same host or another very fast link. Measure with representative data before changing the default.

Error handling

Failures reported by ClickHouse or the native connection derive from ClickHouseTcpException, which in turn derives from DbException: ClickHouseTcpServerException exposes both RawCode, the exact numeric code sent by the server, and Code, the corresponding ClickHouseErrorCode when the driver names it. It also carries the server exception Name, ServerStackTrace, and any nested server exceptions through InnerException.
Codes not represented by ClickHouseErrorCode have Code == ClickHouseErrorCode.Unknown; inspect RawCode without assuming that the enum contains every ClickHouse error. Argument validation and object-lifecycle errors keep their normal .NET exception types. Cancellation raises OperationCanceledException. DialTimeout and ReadTimeout raise ClickHouseTcpConnectionException with an inner TimeoutException, and the connection is discarded. PoolTimeout raises a plain TimeoutException, which is not a ClickHouseTcpException: no connection failed, and the fix is fewer concurrent operations, a larger pool, or finishing result enumerations.

Retrying operations

A connection failure or timeout during an insert leaves its outcome unknown: the server may have committed the rows before the client lost the response. Set a stable DeduplicationToken for the logical batch before retrying it:
Reuse the same token for retries of that batch and use a different token for new data. For other operations, retry only when the statement is safe to repeat and the specific failure is transient; most server errors require changing the query or configuration instead.

Logging and diagnostics

The TCP client integrates with Microsoft.Extensions.Logging. Logging is optional; with no LoggerFactory, the client creates no loggers and formats no log messages.
When the client is registered through the dependency-injection extensions, it uses the container’s ILoggerFactory unless the supplied options already specify one.

Logging categories

Configure the categories through the standard .NET logging configuration:
The operation-start message is at Debug and includes at most StatementMaxLength characters of SQL. The default is five characters. Set the value to zero or less to omit statement text from logs, or raise it only after considering parameters and literals that may contain sensitive data.

Server log messages

The logging integration reports client-side behavior. ClickHouse’s own query log packets are separate and are delivered to ClickHouseTcpQueryCallbacks.OnLog. Enable them with the send_logs_level server setting:
The log block is borrowed and is released when the callback returns. Process it synchronously or copy out values that must be retained. Keep callbacks fast and do not throw; a callback exception terminates the operation and its connection.

OpenTelemetry

The TCP client emits distributed-tracing spans through .NET’s System.Diagnostics.Activity API. Subscribe to the TCP activity source from an OpenTelemetry tracer provider:
For a console application or manual setup:
The activity source name is ClickHouse.Driver.Tcp, separate from the HTTP client’s source, so either transport can be collected independently. When no listener subscribes, operations do not create activities.

Spans and attributes

The client emits spans for SQL operations, pings, and new connections. A SQL span is named after the statement’s leading keyword, such as SELECT or INSERT; statements without a recognizable leading keyword use query. A connect span covers the socket connection, TLS negotiation, and native handshake. Statement spans also include time waiting for a pooled connection. Counters are present only when the server or client has a meaningful value to report. For example, an insert that uploads blocks reports client-sent rows but receives no read counters from the server. Failed spans also contain an exception event with the exception type, message, and stack trace.

SQL text and sensitive data

SQL text is excluded from spans by default. Enable it explicitly and set a suitable limit:
StatementMaxLength caps both the db.query.text attribute and SQL in debug logs. A value of zero or less omits it from both. Review exported exception messages and stack traces as well, because server errors can contain query details.

Trace context propagation

When Activity.Current uses the W3C ID format, the client writes its trace ID, span ID, trace flags, and trace state into the native query packet. ClickHouse can then create server spans in the same distributed trace. Server-side span collection and sampling remain ClickHouse configuration; for example, the sampling probability can be set per query:
The propagated query ID can be used alongside the trace ID to correlate the client span with system.query_log and, when configured, ClickHouse’s system.opentelemetry_span_log.

Limitations

The TCP client is experimental and does not provide every feature of the HTTP client.

HTTP-only features

The TCP API is not an ADO.NET provider and cannot be used with ORMs. It also does not support CSV, JSONEachRow, Parquet, or raw stream input and output; bearer authentication or custom HTTP headers; custom parameter type resolution, formatting, or read conversion; or a per-query database or role. Use the HTTP client when an application needs one of those features.

SQL parameters

Only ClickHouse-native {name:Type} placeholders are supported; the TCP client does not rewrite ADO.NET-style @name placeholders. On ClickHouse 25.8 through 26.6, parameter names that match server settings, such as limit or offset, may be interpreted as settings and rejected. Rename them, for example to row_limit, when supporting those versions.

Data type coverage

The supported data types section describes the complete mappings. The current native codec has these notable boundaries:
  • Tuples can contain at most seven elements.
  • AggregateFunction(function, ...) intermediate states are not supported. Finalize them on the server, for example with sumMerge(column), and read the result instead.
  • Only the two-argument, unstrided QBit(T, N) layout is supported.
  • JSON uses ClickHouse’s String serialization and requires output_format_native_write_json_as_string = 1, which the client requests by default.
  • Application-built inserts have no row-oriented shape for Nested(...) when flatten_nested = 0. Use the default flattened dotted Array(T) columns instead.
  • An application-built Geometry column cannot infer between alternatives that share the same CLR shape, such as Ring and LineString. Insert the concrete geometry type, or reinsert a decoded Geometry column that retains its discriminators.

Automatic retries

The client does not retry failed operations automatically. Retry only operations that are safe to repeat, and set a stable DeduplicationToken before retrying an insert whose outcome is unknown. See retrying operations for details.
Last modified on September 28, 2026