Update dependency ClickHouse.Driver to 1.4.0 - #224
Open
renovate[bot] wants to merge 1 commit into
Open
renovate[bot] wants to merge 1 commit into
renovate[bot] wants to merge 1 commit into
Conversation
renovate
Bot
force-pushed
the
renovate/clickhouse.driver-1.x
branch
from
September 15, 2026 14:05
9b93eb4 to
634ab0c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
1.3.0→1.4.0Release Notes
ClickHouse/clickhouse-cs (ClickHouse.Driver)
v1.4.0Compare Source
Breaking Changes:
Query responses are now compressed with zstd instead of gzip by default. The driver advertises
zstd, lz4, gzip, deflate, and ClickHouse's fixed preference order resolves that to zstd. Bodies are decoded transparently as before, so no code change is needed.Binary inserts are now compressed with zstd instead of gzip by default.
InsertOptions.Compressordefaults toZstdCompressor.Default(level 3) rather thanGZipCompressor.Default(gzip, fastest). You can control this viaInsertOptions.If you set
AcceptEncodingand then read a raw result, you now get compressed bytes.ExecuteRawResultAsync,PostStreamAsyncandInsertRawStreamAsynchand back the response body exactly as the server sent it. CallClickHouseRawResult.ReadDecompressedStreamAsync()to have the driver decompress it, or checkContentEncodingand decompress it yourself. Raw exports with noAcceptEncodingset are unaffected: plaintext before, plaintext now.Reading a column value from
ClickHouseDataReaderwith no current row now throwsInvalidOperationException— that is, before the firstRead()or afterRead()has returnedfalse.New Features:
InsertOptions.QueryPlacement. Set it toInsertQueryPlacement.Urlto send a binary insert'sINSERTstatement as thequeryURL parameter, where proxies and access logs can read it, instead of in the request body; body placement remains the default. If the .NET runtime rejects an oversized URL, the error directs callers back to body placement. (#580)InsertOptions.Compressor(IClickHouseCompressor). The presence of a compressor is the on/off switch: set it tonullto send the payload uncompressed (useful over fast/local links where the compression CPU outweighs the bandwidth savings). GZip, Brotli, LZ4, and ZStd compressors are available.Compressor = nullis worth trying. Over a remote/cloud connection the payload reduction dominates, depending on bandwidth.Lz4Compressor; HTTPContent-Encoding: lz4plus the native-protocol block path). LZ4 now ships in the core driver with no third-party runtime dependency.ZstdCompressor; HTTPContent-Encoding: zstdplus the native-protocol block path).AcceptEncoding— client-wide (ClickHouseClientSettings.AcceptEncoding), in the connection string (AcceptEncoding=lz4), or for one query (QueryOptions.AcceptEncoding).lz4,gzip,deflateandbrare all decompressed for you;identityturns response compression off. Previously onlygzipanddeflateworked: asking forlz4orbrreturned unreadable data, andidentitydid not actually switch compression off. To decompress a raw export, use the newClickHouseRawResult.ReadDecompressedStreamAsync().AcceptEncoding = "zstd"works with every read API instead of failing as an unsupported codec.MapReadMode(connection stringMapReadMode=KeyValuePairs, orClickHouseClientSettings.MapReadMode), which readsMap(K, V)columns asList<KeyValuePair<K, V>>instead ofDictionary<K, V>. A ClickHouse map may repeat a key and a dictionary cannot, so the default representation silently drops pairs. Also accepted on the write path.Improvements:
HttpClientno longer needsAutomaticDecompression. The driver decompresses responses itself, so supplying your ownHttpClient(orIHttpClientFactory) no longer risks unreadable data. If you do setAutomaticDecompression, prefer leaving it at its default ofNone.snappy) now raises an error naming that codec and how to fix it, instead of failing as a confusing type-parse error. Server error messages are now readable when the response is compressed, rather than surfacing as binary.GZipCompressor,BrotliCompressor,Lz4Compressor) no longer allocate a fresh 256 KiBBufferedStreambuffer per batch on the large object heap. The buffering now rents its backing buffer fromArrayPool<byte>.InsertBinaryAsync) now stream the serialized batch directly into the HTTP request body instead of buffering the whole payload. Requests are now sent with chunked transfer-encoding (noContent-Length).int[,],double[,,], …) of fixed-width primitive leaves (Int8/16/32/64,UInt8/16/32/64,Float32/64,Bool) are now serialized by blitting each contiguous inner row in a single write.Int128/UInt128/Int256/UInt256value no longer allocates two temporary arrays per value, and a rectangular multidimensional array whose leaf is a fixed-width primitive behind a wire-transparent wrapper (LowCardinality(Int32),SimpleAggregateFunction(any, Int32), …) now takes the same blit fast path as a bare leaf instead of boxing every element.Nullable(...)leaves keep the per-element path. Output bytes are unchanged.Decimal/Decimal128/Decimal256values (binary inserts and parameters): the write path no longer allocates aBigInteger.ToByteArray()array plus a separate destination buffer per value.objectand unboxing it inside the type'sWrite. Note that if you useRowBinaryWithDefaults, the path is unchanged and still incurs the boxing cost. The change only applies toRowBinary(the default).Variantwrite-type resolution: matching a value to its variant subtype now uses an O(1) hash lookup by runtime type instead of an O(n) linear scan for variants with 3 or more underlying types.BufferedStreambuffer per query. The response read buffering now rents its backing buffer fromArrayPool<byte>and returns it when the reader is disposed. The defaultReadBufferSizehas been raised from 8 KiB to 64 KiB now that pooling removes the per-query allocation that motivated the small default (see v1.3.0), reducing read refills on large responses.Stream.Read(Span<byte>)directly instead of falling through to the base implementation, which rents and copies through a pooled array on every call, and mid-stream exception detection now records from below the read buffer so it observes one read per buffer refill rather than one per value decoded.ClickHouseUriBuilder.ToString()now composes the URI into a thread-reusedStringBuilderinstead of allocating aUriBuilder, two dictionaries, and a LINQ-projected string per parameter. Cuts URI-assembly allocations ~65% on the common parameter-free path. The produced URI string and behavior are unchanged.Int128/Int256/UInt128/UInt256,Decimal128/Decimal256,IPv4, andIPv6no longer allocate a temporarybyte[]per value during deserialization.Int128orUInt128column into a nativeInt128/UInt128property no longer allocates a 16-byte array per value (40 bytes per value on 64-bit).UUID,Array(...)andDecimalcolumns.Dynamiccolumns: every value in aDynamiccolumn carries its own binary type header, so the driver decoded (and allocated) a fresh type descriptor per row. Stateless, parameterless types now decode to a shared immutable singleton instead, eliminating the per-row type allocation.Tuple(...)columns with up to 7 elements:TupleType.Readnow constructs theSystem.Tuple<...>directly through a cached compiled factory instead of allocating two intermediateobject[]buffers and invoking the constructor reflectively viaActivator.CreateInstance.Arraycolumns with common leaf element types (integers, floats,Bool,Decimal, date/time types,UUID,String,Enum,FixedString, and theirNullableforms):ArrayType.Readnow fills a strongly-typedT[]through the array indexer instead of allocating viaArray.CreateInstanceand storing each element with reflectiveArray.SetValue.Mapcolumns now pre-sizes the resultDictionaryto the known entry count.client.QueryAsync<T>(...)): scalar,StringandFixedStringcolumns are now materialized straight into the target property instead of through a boxedobject[]row buffer, removing one box and one unbox per value-type property per row.SimpleAggregateFunction(f, T)columns now take the box-free fast path inQueryAsync<T>, asLowCardinality(T)already did. Previously they fell back to the slower boxed read.QueryAsync<T>can now bind one column to more than one CLR type, chosen by the property:DateTime/DateTime64/DateintoDateTime,DateTimeOffsetorDateOnly;String/FixedStringintostringorbyte[];DecimalintodecimalorClickHouseDecimal;Enum8/Enum16intostring(the label) orint(the stored numeric value); and, on .NET 8+,Int128/UInt128into the nativeSystem.Int128/System.UInt128as well asBigInteger.client.QueryAsync<T>(...)can now read aMap(K, V)column into aList<KeyValuePair<K, V>>orKeyValuePair<K, V>[]POCO property, in addition toDictionary<K, V>.IReadValueConverterconfigured,client.QueryAsync<T>(...)applies it per column on the overload that matches how the column was read:ConvertValue<T>, withTthe property type, for a column read without boxing, and the boxedConvertValuefor a composite one.ClickHouseDataReadertyped accessors (GetInt64,GetDouble,GetDateTime,GetGuid, …),GetFieldValue<T>,IsDBNulland ORMs such as linq2db by decoding scalar columns into reusable typed slots instead of eagerly boxing every value-type cell. Allocations drop by roughly two thirds on a typical multi-column typed read and to zero per row for all-numeric typed reads.IReadValueConverterconfigured,ClickHouseDataReader's typed accessors (GetInt64,GetDouble,GetDecimal,GetString, …) keep their reduced allocations: each reads its typed slot and converts throughConvertValue<T>, the overloadGetFieldValue<T>uses. These accessors previously routed throughGetValueand so through the boxedConvertValue.ClickHouseDataReader.TryGetEnumOrdinal(int ordinal, out int value)for reading the underlying integer ordinal of anEnum8/Enum16(orNullable(Enum...)) column.UseFormDataParametersthrough the connection string. AddedUseFormDataParametersproperty toClickHouseConnectionStringBuilder.Mapcolumns now builds the resultDictionarythrough a cached compiled factory instead of a reflection invoke, removing the per-row constructor resolution that the pre-sizing change added.Deprecations:
ClickHouseClient.MemoryStreamManageris now[Obsolete]. Since binary inserts stream directly into the request body (see above), this property is no longer used and has no effect; it will be removed in a future version.Bug Fixes:
JSONcolumn with overlapping paths silently losing data. ClickHouse accepts a column which declares a path both as a value and as the parent of another path — for exampleJSON(a Int64, a.b Int64)— and sends both paths in every row, so the row renders as a document with a duplicate key ({"a":0,"a":{"b":7}}). AJsonObjectcannot hold two values for one key, so the driver silently kept whichever path the server sent last: reading{"a":{"b":7}}back out of that column returned{"a":0}and the7was gone. The same loss hit aMappath overlapped by a deeper path (JSON(a Map(String, Int64))holding{"a":{"b":1},"a.b":7}returned{"a":{"b":7}}, dropping the map's own entry), except that there the two values were merged into one object rather than one replacing the other. A row where both sides of such an overlap hold a value now throws aSerializationExceptionnaming the two paths. With non-Nullablepaths the driver cannot tell a stored0from an absent path, so no rule can recover the real value — the choice is between a wrong answer and a clear failure. A side which holds nothing is not a collision: a null, an empty object, and an all-null subtree all give way to the side which has the data, whichever of the two paths the server sends first, soNullableoverlaps continue to read as before. UseJsonReadMode.Stringto read such a column as the server's JSON text, duplicate key included. To restore the previous behavior and keep reading the column as aJsonObject, set the newAllowDuplicateJsonKeyssetting (connection string keyAllowDuplicateJsonKeys=true, orClickHouseClientSettings.AllowDuplicateJsonKeys); the driver then keeps whichever value the row carries last, with the data loss described above. A path which holds a value and whose parent holds a scalar or an array still throws — that shape raisedInvalidCastExceptionbefore and is now aSerializationException. (#583)InsertOptions.Compressor = null) inflating the HTTP request body. The request body is sent chunked, and the row serializer wrote straight to it, so every field write became its own HTTP chunk.JSONPOCOs, andString/FixedStringvalues given as aStream.InsertOptions.WithColumnTypes()andInsertOptions.WithQueryId()silently dropping some caller-set options (such asAcceptEncoding) when copying.JSONcolumns against the wrong typed-path hints when the same POCO type is inserted into two tables whoseJSONcolumns declare different hints. The second insert reused the first table's cached write delegates, so values were written silently mis-typed.ClickHouseServerExceptioncarrying a blankMessageand anErrorCodeof-1when the server — or an upstream component such as a load balancer or the ClickHouse Cloud edge — returned a non-2xx HTTP response with an empty (or whitespace-only) body. The exception now reports the HTTP status code and reason phrase, and uses theX-ClickHouse-Exception-Coderesponse header as the error code when the server sets it. Non-empty error bodies are unaffected.ClickHouseClientleaking the query'sHttpResponseMessageon the paths that fully consume it (ExecuteNonQueryAsync,InsertBinaryAsync) and on any request that fails. Paths that hand the response to the caller (ExecuteReaderAsync/ExecuteRawResultAsync/InsertRawStreamAsync/PostStreamAsync) are unchanged.ClickHouseClient.InsertRawStreamAsyncdisposing the suppliedStreamtwice when the request failed: the request message already disposes the content it carries, so the extraDispose()in the failure path was redundant. The stream-taking write paths now document that the supplied stream is disposed once the request completes.PingAsyncignoring thePathconnection setting: the ping request now targets<Path>/ping, so clients behind a reverse-proxy prefix no longer report a healthy server as unreachable.ExecuteReader/ExecuteReaderAsync). A query that fails after the HTTP response is committed (for example athrowIfpartway through a large result) now raises aClickHouseServerExceptionwith the real server error, instead of a bareHttpIOExceptionorEndOfStreamException.ClickHouseCommandreturning wrong results (or a syntax error) forCommandBehavior.SchemaOnlyandCommandBehavior.SingleRowwhenCommandTextended with a single-line comment (--/#) or a statement-terminating;.ClickHouseDataReader.GetSchemaTable()leavingNumericScaleunset (DBNull) forDateTime64(N)andTime64(N)columns (including theirNullable(...)variants). The schema table now reports the fractional-seconds precisionNinNumericScale, matching howDecimalcolumns are already reported.DbConnection.GetSchema("Columns", ...)not disposing the command it creates internally, which delayed the release of that command's cancellation-token source until garbage collection.ClickHouseConnection.GetSchema("Columns", ...)building invalid SQL when the table restriction is supplied without the database restriction (for example[null, "functions"]). TheWHEREclause is now composed from the restrictions that are actually set, so filtering by table alone no longer fails with a server syntax error.ClickHouseConnection.GetSchema("Columns", ...)silently ignoring restriction values beyond the supporteddatabaseandtablepositions.max_dynamic_pathsormax_dynamic_typesbeing mistaken for JSON settings and decoded as dynamic values.JSONcolumns being unreadable when a typed path name requires backtick quoting — for exampleJSON(`a b` Int64)orJSON(`a,b` Int64). Such a path made the whole query fail withSerializationException: Unsupported path in JSON hint, because the type parser split each hint on every space and did not treat backticks as quotes. Quoted path names (including ones containing spaces, commas, parentheses and escaped characters) are now parsed and unescaped correctly.JSONcolumn where a typed path holdsNULL. The path was dropped from the returnedJsonObjectentirely, so{"x": null}came back as{}and callers could not tell "path not present in this row" from "path present but null"; for a nested typed path such asJSON(a.b Nullable(Int64))the whole parent subtree disappeared. Typed paths are now materialized with an explicit JSON null, matching the server's own JSON rendering. Dynamic (unhinted) paths are unchanged and stay absent, as the server also omits them.JSONcolumn being returned as base64 whenReadStringsAsByteArrays = true, andMap(String, ...)keys throwingInvalidCastException. String leaves inside aJSONcolumn are now always decoded as UTF-8 text regardless of the setting — remove any base64 workaround; the setting is unchanged for ordinaryString/FixedStringcolumns.TupleandNestedcolumns being unreadable when an element name requires backtick quoting and contains a space — for exampleTuple(`p q` Int64, r String)orNested(`a b` Decimal(10, 2), c String). Reading such a column failed withArgumentException: Unknown type, because the type parser split the element declaration on its first space and so cut the quoted name in half. The element name is now skipped as a whole before the name/type separator is located.Tuple()column type that ClickHouse accepts and reports back insystem.columns.byte[]/ReadOnlyMemory<byte>bound toString/FixedString(abyte[]was sent as the literal textSystem.Byte[]) and rejecting aTimeOnlybound toTime/Time64. Byte payloads are now escaped byte-for-byte, so data that is not valid UTF-8 round-trips losslessly;TimeOnlybinds on both the HTTP and binary write paths, and infers asTime64(7)when no type hint is given.{name:Type}parameter type hints being mis-detected in queries containing//comments, nested block comments, backtick/double-quoted identifiers, backslash escapes or$tag$heredocs. A bare#no longer starts a comment (only#and#!do).{name:Type}parameter type hints being dropped, or a hint being invented for a parameter that does not exist, when the query contains another{that is not a type hint — for example aSETTINGSmap value such asadditional_table_filters = {'t': 'a > 0'}. A dropped hint fell back to CLR-type inference, losing precision.@nameparameter placeholders being rewritten inside string literals, quoted identifiers, heredocs and comments, which corrupted values such as'user@id'into'user{id:Int32}'. Placeholders are now only replaced in code positions.$inside an unquoted identifier being mistaken for the start of a$tag$heredoc. Everything up to the next occurrence of the same$...$text was skipped as heredoc body, silently dropping any{name:Type}type hint (and any ADO-style@nameplaceholder) in between — for example inWITH 1 AS b$c$ SELECT {d:Date} AS v, b$c$ AS x. A heredoc is now only recognized where a token starts, matching the server lexer.@nameplaceholders not working when the parameter name contains a$, which ClickHouse accepts in a query parameter name:@id$xcould not be bound at all (the name was interpolated into a regex, where$is an end-of-input anchor), and a shorter name won over a longer one, so with onlyiddefinedSELECT @id$xwas silently rewritten into a different, still valid query that aliased the value as$xinstead of being left for the server to reject. A$is now part of the placeholder name, matching the server lexer.Configuration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.