Release Migration Guide
Breaking changes
-
[client-v2, jdbc-v2] The socket buffer options
socket_rcvbufand
socket_sndbufhave no default value anymore. Now the options are applied only when the application sets them
(Client.Builder#setSocketRcvbuf,
Client.Builder#setSocketSndbuf, or the properties of the same name). This is done to let OS TCP Stack auto-tune
values. Do not set them until absolutely needed. (#3121) -
[client-v2, jdbc-v2] Server error
159(TIMEOUT_EXCEEDED) is not retried by default anymore. It has its own
retry cause,ClientFaultCause.ServerTimeoutExceeded, whichServerRetryabledoes not include and which is not in
the defaultclient_retry_on_failureslist. AddServerTimeoutExceededto the list to retry this error.
(#3072) -
[r2dbc] Pre
1.0.0artifact is not supported anymore because R2DBC API reached stable1.0.0version. -
[jdbc-v2]
DatabaseMetaData#getTablesnow returnsnullinREMARKS(table comment) andTYPE_SCHEMby
default, because metadata is read withSHOWstatements (see thejdbc_metadata_use_show_statementsentry in New
Features). Setjdbc_metadata_use_show_statements=falseto get the table comments.
(#2907) -
[client-v2,jdbc-v2] Added ZSTD compression support for Block compression stream and used by default not to
compress client requests. Previously only LZ4 was supported in this case. Note: Added ZSTD library and native
libraries to-allJDBC package because it is now required to work with server.
(#3105).
Important Changes
-
[client-v2]
com.clickhouse.client.api.observability.SpanSupportnow usesQUERYandINSERToperation
constants (QUERY <database>andINSERT <database>.<table>span names).db.operation.nameattribute is set to
INSERTfor insert operations and left unset for queries because SQL statements are not parsed on the client. -
[client-v2]
com.clickhouse.client.api.metrics.OperationMetricsnow has a single constructor,
OperationMetrics(ClientStatisticsHolder, OperationType); the constructor without an operation type was removed.
Metrics are created by the client, which always knows the kind of the operation it runs, and the constructor takes
an internal type (com.clickhouse.client.api.internal.ClientStatisticsHolder), so application code is not expected
to call it. (#2974)
New Features
Data Types
-
[client-v2, jdbc-v2] Added support for the
MultiPointgeo data type (ClickHouse26.8+). Previously the type
was unknown to the client, so reading or writing aMultiPointcolumn failed withUnknown data type: MultiPoint,
and aMultiPointvalue inside aGeometrycolumn failed with an out-of-range variant discriminator.MultiPointis
Array(Point)on the wire, exactly likeRingandLineString, so it is read and written asdouble[][]through
generic records, binary readers, POJO binding, and SQL parameter formatting, and is read fromDynamiccolumns. In
the JDBC driver (jdbc-v2)MultiPointmaps tojava.sql.Types.ARRAY, is returned asdouble[][]fromgetObject
and as ajava.sql.ArrayfromgetArray, and is reported byResultSetMetaDataandDatabaseMetaData. ClickHouse
26.8also addsMultiPointto theGeometry
variant; the server appends it after the existing six variants instead of ordering it by type name, so the client now
keeps that order and decodes aMultiPointheld in aGeometrycolumn. BecauseMultiPointshares its Java
representation (double[][]) withRingandLineString, it is not selectable through the shape-basedGeometry
write path — a 2D value keeps resolving toRingas before, and writingMultiPointrequires a concreteMultiPoint
column. (#3048) -
[client-v2, jdbc-v2] Added support for the
BFloat16data type (ClickHouse24.11+).BFloat16columns are read as
Javafloatvalues (widening is lossless) and written fromfloat/Floatvalues, including through generic records, POJO
binding,Nullable(BFloat16), andBFloat16values held inDynamic/Variantcolumns. On write the client keeps the
high 16 bits of thefloat, matching the ClickHouse server's ownFloat32→BFloat16conversion. In the JDBC driver
(jdbc-v2)BFloat16maps tojava.sql.Types.FLOAT/java.lang.Floatand is read and written through the standard
getFloat/setFloatandgetObjectaccessors, and reported as such byResultSetMetaDataandDatabaseMetaData.
Previously reading or writing aBFloat16column failed with an
unsupported-data-type error. (#2279) -
[client-v2, jdbc-v2] Added support for the experimental
QBit(element_type, dimension[, stride])vector data type
(ClickHouse25.10+; theallow_experimental_qbit_typeserver setting is required to create a column). The type-name
parser accepts two or three parameters (the optional third is the stride) and recognizes the documented element types
Int8,BFloat16,Float32, andFloat64; an element type outside that set is parsed with a warning rather than
rejected, so a newer server-side element type keeps parsing. AQBitvalue is transmitted overRowBinaryexactly like
Array(element_type), so it is read and written as a Java array of the element type (float[]for
BFloat16/Float32,double[]forFloat64) through generic records, binary readers, and POJO binding, via a
dedicatedQBitread/serialize path. AQBitheld inside aDynamic/Variant/JSONcolumn is also decoded (its
binary type encoding is read back to the concreteQBit(...)type). In the
JDBC driver (jdbc-v2)QBitmaps tojava.sql.Types.ARRAYand is returned as ajava.sql.Arrayfrom
getObject/getArray. PreviouslyQBitwas an unimplemented type constant and reading or writing such a column
failed. A plain top-levelQBitcolumn with aFloat32,Float64, orBFloat16element type is also read through
theNativeoutput format: there the server transmits it using its internal bit-plane-transposed
Tuple(FixedString(...))layout, and the client reverses that transposition to reconstruct the same
float[]/double[]vector asRowBinary. AQBitthat is strided (QBit(element_type, dimension, stride)),
wrapped inNullable/LowCardinality, nested inside another type (e.g.Array/Tuple/Map(String, QBit(...))),
or carrying any other element type is not yet decoded overNativeand fails fast with a clear error directing you
to aRowBinaryformat such asRowBinaryWithNamesAndTypes.
(#2610)
Observability
-
[client-v2, jdbc-v2] Added a Micrometer implementation of the metrics SPI.
Client.Builder.setMetricsRecorder(new MicrometerMetricsRecorder(meterRegistry))reports the metrics of every client
operation to a MicrometerMeterRegistry: a timerdb.client.operation.durationper completed operation, a timer
clickhouse.client.operation.serialization.durationwhen the client measured the serialization step, a counter
clickhouse.client.operation.countper completed operation, and a counterclickhouse.client.operation.retriesper
retried attempt. Previously the client could bind only its connection-pool gauges to Micrometer, so exporting the
metrics of the operations themselves was left to the application. Meter names, units, descriptions and tag keys are
the standard ones of the SPI - the recorder derives them throughMetricsSupport, so they are the names of
MetricNameand the keys ofMetricAttributeand mean the same as for every other recorder. A successful operation
carries noerror.typetag and a failed one does, so the outcomes are separate time series of the same meter and a
failure the server reported also carriesdb.response.status_code; a duration the client did not measure is not
recorded, so no operation is reported with a made-up duration. The seconds of the SPI are handed to the registry as
nanoseconds, because a Micrometer timer keeps its own time unit, so a backend publishes the duration in the unit it
expects. The no-argument constructor reports toMetrics.globalRegistry, which is what the jdbc-v2
jdbc_metrics_recorderproperty needs, so a JDBC connection exports its metrics to Micrometer by naming the class -
jdbc_metrics_recorder=com.clickhouse.client.api.observability.micrometer.MicrometerMetricsRecorder- without
application code.micrometer-corestays an optional dependency ofclient-v2and is not shaded into theall
artifacts, so a client that does not use this recorder needs no Micrometer on the classpath.
(#2975) -
[client-v2, jdbc-v2] Added a metrics SPI that lets an application export the metrics of client operations to any
metrics backend.Client.Builder.setMetricsRecorder(MetricsRecorder)registers a backend-agnostic recorder from the
com.clickhouse.client.api.observabilitypackage, and the jdbc-v2 propertyjdbc_metrics_recordernames the recorder
class a connection registers with its own client. Previously the client collected operation metrics but only returned
them to the caller, so exporting them was left to the application. Each completed operation reports exactly one
success or one failure event, and each retried attempt reports a retry event, which gives the operation duration, the
serialization duration, the number of operations by outcome and the number of retries. The SPI follows the pattern of
the span SPI: an implementation extends theDefaultMetricsRecorderbase class and overrides only what it cares
about, so it keeps working when the client starts reporting an event it does not know about, and the reusable
MetricsSupportclass derives the standard values from the same structures, so its logic is opt-in and overridable.
Metric names, units and attribute keys follow the OpenTelemetry semantic conventions for database clients where a
convention exists and are placed underclickhouse.where it does not; they are defined by theMetricNameand
MetricAttributeenums, durations are reported in seconds, and a duration the client did not measure is reported as
MetricsSupport.DURATION_UNKNOWNinstead of a made-up value. The metric attributes are deliberately a smaller set
than the span attributes, because an attribute of a metric becomes a time series: the statement text, the query id and
the statement parameters stay on spans. Nothing is recorded and no metrics-related work is done when no recorder is
registered. (#2975) -
[client-v2] Added an OpenTelemetry implementation of the observability SPI.
Client.Builder.setSpanRecorder(new OpenTelemetrySpanRecorder(openTelemetry))
reports every client operation and every transport request as an OpenTelemetryCLIENTspan: an operation span is
started as a child of the current OpenTelemetry context, so it joins the application's own trace, and each request
span - including one per retry - is a child of its operation span. Span names and attribute keys are the standard ones
of the SPI (the recorder derives them throughSpanSupport), every value is recorded with the OpenTelemetry attribute
type that matches it, and a failure sets the span status toERRORand is recorded as an OpenTelemetry exception
event next to theerror.typeanddb.response.status_codeattributes. The recorder reports to a supplied
OpenTelemetryinstance, to aTracergiven tonew OpenTelemetrySpanRecorder(Tracer), or to
GlobalOpenTelemetry- read when a span is started - when constructed without arguments. Previously an application
that wanted OpenTelemetry spans had to write that mapping itself. The OpenTelemetry API is a compile-only dependency
of
client-v2: the recorder is used only by an application that already providesopentelemetry-apiat runtime, so
nothing is added to the classpath of a client that does not use it.
(#2974) -
[client-v2] Added an observability SPI that lets an application observe client operations as spans.
Client.Builder.setSpanRecorder(SpanRecorder)registers a backend-agnostic recorder from the new
com.clickhouse.client.api.observabilitypackage: each operation (a query, a command or an insert - including the
pingandgetTableSchemacalls, which run a query) starts one operation span, and every transport request made for
it - including each retry - starts a child request span.SpanRecorderandSpanare plain interfaces; an
implementation extends the
DefaultSpanRecorderbase class and overrides only what it cares about, so it keeps working when the client starts a
kind of span it does not know about. The registered recorder is called first and receives everything the client knows
about the operation (itsQuerySettings/InsertSettings, the statement, the target table, the batch size, the
endpoint, the metrics of the completed operation and the failure), so it is free to record whatever it needs and in
whatever form; the reusableSpanSupportclass derives the standard span names and attribute values from those same
structures and is called by a recorder implementation that wants them, so its logic is opt-in and overridable. Span
names and attribute keys follow the OpenTelemetry semantic conventions for database and HTTP client spans; the keys
are defined by theSpanAttributeenum and the values are derived by
SpanSupport, so all recorders that use it report the same information (statement text, target database and table,
query id, statement parameters, batch size, the first configured endpoint on the operation span and the per-attempt
server address and port on the request spans, HTTP status, returned rows, and the error type and ClickHouse error code
on failure). The outcome of a completed operation is reported per operation kind -recordQuerySuccess
for a read andrecordInsertSuccessfor an insert - because the metrics that describe a read are not the ones that
describe a write: a query reportsdb.response.returned_rows,clickhouse.response.read_rowsand
clickhouse.response.read_bytes, an insert reportsclickhouse.response.written_rowsand
clickhouse.response.written_bytes. The same distinction is available on the metrics themselves through the new
OperationMetrics#getOperationType(), which returns the newcom.clickhouse.client.api.metrics.OperationType- the
kind of the call the application made, so a command that writes is reported as a query. An operation span is started
on the calling thread, so it joins the caller's ambient trace even when the operation runs on the client's executor,
and it is ended exactly once for every operation that starts. Previously the client exposed no hook for tracing, so an
application could not attribute a query or a retried request to its own trace. When no recorder is registered nothing
is recorded and no span-related work is done, so the default path is unchanged. An OpenTelemetry implementation of the
SPI is available asOpenTelemetrySpanRecorder. (#2974)
Improvements
-
[migration-helpers] Added
migration-helpersmodule containingConfigurationMigrationHelperand
ConfigPropertyCacheto convert configuration properties and connection URLs from v1 (0.7.1) format to v2 (0.9.8+)
format (automatically prefixing ClickHouse server settings withclickhouse_setting_, custom headers with
http_header_, and mapping renamed property keys). -
[client-v2, jdbc-v2] Added TLS cipher suite selection.
Client.Builder.setSSLCipherSuites(String...)(client-v2)
and the comma-separatedssl_cipher_suitesconnection property (client-v2 and jdbc-v2) restrict the cipher suites
enabled on secure connections; when unset, the transport defaults are used. Cipher-suite selection is independent of
the trust configuration andssl_mode. (#2882) -
[client-v2, jdbc-v2] Added support for un-flattened
Nested(...)columns (tables created with
flatten_nested = 0). Previously theRowBinarywriter threwUnsupportedOperationException: Unsupported data type: Nestedwhen inserting such a column.client-v2now serializes aNested(f1 T1, ..., fN TN)
column the same way it is read — identically toArray(Tuple(T1, ..., TN))(a var-uint row count followed by one
tuple per nested row) — so it can be written through the insert path /RowBinaryFormatWriter. In
jdbc-v2an un-flattenedNested(...)column is exposed as a JDBCARRAYwhose element type is
Tuple(f1 T1, ..., fN TN): it can be inserted throughConnection#createArrayOf/setArrayorsetObject
and read back throughgetArray/getObject, andjava.sql.Array#getResultSet()iterates the nested rows as
(INDEX, VALUE)pairs where eachVALUEis the tuple. (#2477) -
[client-v2, jdbc-v2] Added logging on previously-silent error and diagnostic paths (no functional or public-API
change). (#2969) -
[jdbc-v2]
DatabaseMetaData#getSchemas,#getTablesand#getColumnsnow read metadata withSHOW DATABASES,
SHOW TABLESandDESCRIBE TABLEinstead of thesystem.databases,system.tablesandsystem.columnstables. The
server does not show some tables in the system tables by default (for example, tables ofDataLakeCatalog
databases), so tools that useDatabaseMetaDatadid not find them. The new driver property
jdbc_metadata_use_show_statements(defaulttrue) selects the implementation; set it tofalseto use the system
tables. With theSHOWstatements,getTables()returnsnullinREMARKS(table comment) andTYPE_SCHEM;
getColumns()sends oneDESCRIBE TABLEquery for each table that matches and skips a table that was dropped
meanwhile, that the user cannot describe, or whose data lake metadata cannot be read. All other columns and values are
the same. (#2907)
Bug Fixes
-
[jdbc-v2] Fixed an
INSERT ... VALUESstatement whose values list holds a literal, an expression (e.g.? + 1)
or, with theJAVACCparser, a JDBC escape sequence or a query parameter, being written with the RowBinary writer
whenbeta.row_binary_for_simple_insertis enabled. The writer takes one bound value per column, so the bound values
were shifted to other columns: the statement failed with a misleading error, or stored wrong data with no error. The
parsers did not report such values: theJAVACCparser discarded the result of its values-list check, and the
ANTLR4parsers reported only function calls. Now the writer is used only for a values list of?placeholders, and
other statements use the standardPreparedStatementpath.
(#3083) -
[client-v2,jdbc-v2] - Replaced slow HTTP LZ4 compression with compressing stream from
lz4-javalibrary. Client uses Apache Compress to handle HTTP compression (because it has convenient factory for many
compressions methods. However, Apache Compress uses slow LZ4 implementation what causes very slow inserts. Now
lz4-javaused for insert. Query still slow but will be fix in future releases (need refactoring and custom
implementation to solve it). UsingZSTDfor queries should solve the issue.
(#2273). -
[client-v2] Fixed writing a
Stringvalue into aUUIDcolumn (also as anArray/Tupleelement or aMap
key) failing withClassCastException. The string is now parsed withUUID.fromString; a string it cannot parse is
rejected on the client with anIllegalArgumentException. (#3132) -
[client-v2] Fixed reading a
Nullablecolumn in theNativeformat failing withFailed to read block ... End of stream reached before reading all data, or returning values of the wrong rows. The reader consumed a null
marker before every value, which is the RowBinary layout.Nativeis columnar: it stores a null map of one byte per
row before the values of the whole column, and a null row still has a placeholder value. The markers were therefore
taken from the value bytes and the column was read out of alignment. The null map is now read as a block.
(#3137) -
[client-v2] Fixed geo columns (
Point,Ring,LineString,MultiPoint,Polygon,MultiLineString,
MultiPolygon) being misread from theNativeformat. The reader decoded a geo column row by row with the RowBinary
decoders, whileNativewrites it column-major: aPointblock came back with its coordinates scrambled across rows
and no error, and every other geo type desynchronized the block and failed with
Non-empty typeName is required. A geo column is now decoded from the Native layout - the twoFloat64
sub-columns of a point, and cumulative offsets plus the flattened elements for the array levels - and returns the same
values asRowBinaryWithNamesAndTypes, which is unchanged.
(#3088) -
[client-v2, jdbc-v2] Fixed a column type with a
JSONelement that is followed by a parameterized type, for
exampleTuple(JSON, FixedString(3)), being parsed wrongly.JSONis valid with and without a parameter list, and
the parser looked for the opening bracket of that list anywhere after the keyword, so it took the brackets of the next
element for the parameters of theJSONelement. Everything up to those brackets was consumed, which dropped the
elements between them, or failed withUnknown data type: <parameter>when the following type had more than one
parameter, for exampleTuple(JSON, Decimal(10, 2)). Reading such a column, andClient#getTableSchemaof a table
that has one, failed or returned an incomplete type. A parameter list is now recognized only when it immediately
follows theJSONkeyword. (#3098) -
[jdbc-v1] Fixed
WITH RECURSIVE <name> AS (...)failing to parse. The JavaCC grammar did not know the
RECURSIVEkeyword, so it was taken for the name of the first common table expression and the statement was rejected.
The query itself still ran, because the driver falls back to sending the original SQL, but every
createStatement/prepareStatementcall logged aWARNparse failure and the statement was classified as unknown.
RECURSIVEis now accepted afterWITHand stays usable as an ordinary identifier (column, alias, table or CTE
name). (#3122) -
[jdbc-v2] Fixed
WITH RECURSIVE <name> AS (...)failing to parse. Neither SQL grammar knew theRECURSIVE
keyword, so it was taken for the name of the first common table expression and the statement was rejected. The query
itself still ran, because the driver falls back to sending the original SQL, but everycreateStatement/
prepareStatementcall logged a parse failure (WARNwith the JavaCC backend) and the statement was classified as
unknown.RECURSIVEis now accepted afterWITHby both the JavaCC and the ANTLR4 grammars, and stays usable as an
ordinary identifier (column, alias, table or CTE name). (#3122) -
[jdbc-v2] Fixed
ArrayResultSet#next()leaving the cursor before-first for empty arrays or on the last row for
non-empty arrays after exhaustion. The cursor now moves to the after-last state whennext()returnsfalse. -
[jdbc-v2] Added the non-reserved keywords
AGGREGATE,BOUNDED,EXTEND,HANDLER,IDLE,PROTOCOL,
RECENT,TIMEOUTandUNORDERED(ClickHouse26.8+;IDLE,TIMEOUTandRECENTcome from the multi-word
keywordsIDLE TIMEOUTandRECENT SAMPLES) to the list of keywords allowed in identifier positions. The server
accepts all of them as a column or table alias, so a query using one of them as an identifier must parse.
(#3113) -
[client-v2] Fixed a query with statement parameters sent in the request body
(client.http.use_form_request_for_query=true) failing withLZ4 decompression failed ... (LZ4_DECODER_FAILED)
when client request compression and HTTP compression were both enabled. The multipart body is always sent
uncompressed, but the request still declaredContent-Encoding: lz4; ClickHouse26.8+honours that header for
multipart requests and tried to decompress a plain body. The header is now omitted for multipart requests, like the
decompressquery parameter already was. Response compression (Accept-Encoding,
enable_http_compression) is unchanged. (#3075) -
[client-v1] Fixed the
DateTime64case oftestReadWriteSimpleTypesfailing against ClickHouse 26.8. From 26.8
an unquoted number written to aDateTime64column in theValues/QuotedandJSONpaths is a Unix timestamp in
seconds instead of the raw scaled value - the server settinginput_format_read_datetime_number_as_raw_value
changed its default from1to0- so theinsert into ... values(1)of the test stored1970-01-01 00:00:01
instead of the expected1970-01-01 00:00:00.001. The test now writes a quoted date-time literal forDateTime64, as
it already does forFixedStringandUUID, so the written value means the same on every server version and the
sub-second round-trip stays covered. No client code is affected: both clients quote a date-time value in a text
statement or send it inRowBinary. (#3114) -
[jdbc-v2, client-v2] Fixes issue with
FORMATin query unable to override format set by client when used with
ClickHouse 26.8+. Default format isRowBinaryWithNamesAndTypesset at client level. For JDBC, recommend using
format=JSONEachRowto query JSON. Settingformat=(empty ornull) omits the format request header so explicit
queryFORMATclauses take effect; note that on JDBC any statement without aFORMATclause will fail because the
server falls back todefault_format(TabSeparated).DatabaseMetaDatais unaffected: every statement it runs
internally pinsRowBinaryWithNamesAndTypesin its own settings, so metadata keeps working regardless of the
connection'sformatproperty. (#3086) -
[jdbc-v2] Fixed
Connection#prepareStatementandPreparedStatement#addBatchthrowing
StringIndexOutOfBoundsExceptionfor anINSERT ... VALUES (...)statement containing a JDBC escape sequence
({d '...'},{ts '...'}, ...) or a ClickHouse query parameter whose name starts withd/t(e.g.{d:Int32}).
The defaultJAVACCparser records the values list positions as offsets into the SQL it rebuilds from the token
stream, where such sequences are rewritten or dropped, while the driver slices the original SQL with them — so the
slice was taken at the wrong offsets or past the end of the statement. The positions are now checked against the
original SQL and discarded when they do not address its values list, in which case the driver falls back to its
generic parameter substitution path. Such a statement is now prepared without error; the escape sequence itself is
still sent to the server unchanged. TheANTLR4parser backends were not affected.
(#3017) -
[jdbc-v2] Fixed a
?inside a//line comment or inside a heredoc (dollar quoted string, e.g.$$...$$or
$tag$...$tag$) being counted as aPreparedStatementparameter. Such a statement expected a value the application
could not supply, soexecuteQuery()failed withParameter at position 'N' is not setfor a query the server
executes fine. The placeholder scan now skips both token kinds, like the server lexer does; a$that does not open a
heredoc is still treated as an ordinary character (it is a valid identifier character).
(#3009) -
[jdbc-v2] Fixed
INSERT INTO [TABLE] FUNCTION f(...) VALUES (?)failing with
Code: 60 ... does not exist. (UNKNOWN_TABLE)when thebeta.row_binary_for_simple_insertfeature was enabled.
Neither SQL parser reported a table-function insert target as a function, so the statement was routed to the
RowBinarywriter, which looked the function name (or a placeholder such asunknown) up as a table. Both parsers
now report such a statement as using a function, so it stays on the regular SQL path; additionally the JavaCC grammar
no longer mis-parsesINSERT INTO TABLE FUNCTION f(...)by consuming
FUNCTIONas the table name. Inserts into a plain table are unaffected and still use theRowBinary
writer. (#3015) -
[jdbc-v2] Fixed
Connection#prepareStatementthrowing aNullPointerExceptionfor an
INSERT ... VALUES (...)statement whose values list the default JavaCC parser cannot parse — most commonly one
containing a heredoc string ($$...$$), which the grammar has no token for, but also any other unparsable token
inside the list. The parser's error recovery left the values list's start position recorded without its matching end
position, which was then unboxed unguarded. Both positions are now dropped together, so the driver falls back to its
generic parameter-substitution path and such statements are prepared and executed successfully. TheANTLR4
parser backends were not affected. (#3013) -
[client-v2] Fixed reading a
JSONor namedTuplevalue nested in aDynamiccolumn when a typed path or
element name requires quoting (it contains a space, a comma or a bracket). Names read from the binary type encoding
were appended to the reconstructed type name unquoted, so e.g.JSON(`a b` Int64)inside aDynamiccolumn
produced a malformed type name and the whole query failed withIllegalArgumentException: Unknown data type: b Int64.
Such names are now back-quoted (with inner back-quotes escaped) exactly as the server renders them, andJSONskip
paths and path regexps are emitted with theirSKIP/SKIP REGEXPmarkers. Names that need no quoting are rendered
as before. Top-levelJSONcolumns andJSONnested inMap/Tuple/Arraywere not affected — their type comes
from theRowBinaryWithNamesAndTypesheader, which the server already quotes.
(#3001) -
[client-v2] Fixed reading a
Variant,Nested,DecimalorEnumvalue held in aDynamiccolumn. The
concrete type rebuilt from the binary type encoding did not match what the server encoded:Variantwas wrapped twice
(so the discriminator selected the wrong element),Nestedread only the element names and left the element type
encodings in the stream, andDecimal/Enumlost their precision and scale / their constants whenever the value sat
inside another type, so a decimal read back unscaled (1.2500as12500) and every enum value read back as
<unknown>. The constant width of an enum is now taken from the type tag rather than from the number of constants,
which also fixes reading anEnum16with fewer than 128 constants and negativeEnum8constants.
(#3003) -
[client-v2] Fixed a
Nullable(T)column bound to a primitive POJO field silently corrupting a row on the POJO
read path. The compiled setter went straight to a primitive read method without consuming theNullable
null-marker byte, which is on the wire for every value of a nullable column regardless of the value, so the stream
stayed shifted by one byte per row and the nullable column and every column after it decoded from the wrong offset
without any error being raised. The generated setter now consumes the marker; a value that is actuallyNULLcannot
be held by a primitive field and is reported with aNullValueException. Boxed POJO fields are unaffected.
(#2993) -
[client-v2] Fixed the
Nativeformat reader (NativeFormatReader) misreadingArraycolumns in multi-row
results whose rows have different lengths. Native encodes an array column as cumulative row offsets followed by the
flattened elements, but the reader used the first row's offset as the element count for every row — truncating later
rows and desyncing the columns that follow the array in the same block. Each row's length is now derived from the
difference between consecutive offsets, and empty array rows (len == 0) no longer read a phantom element. Results
with uniform array lengths were unaffected. (#2955) -
[jdbc-v2] Fixed
SQLException#getSQLState()returning the generic data-exception state22000
when ClickHouse reports an unknown table. The driver now returns42S02(base table or view not found) while
preserving the ClickHouse error code and original exception.
(#3104) -
[client-v2] Fixed truncated LZ4 stream errors reporting literal
{0}and{1}placeholders instead of the number
of bytes read and expected. (#3108) -
[jdbc-v2] Fixed
DatabaseMetaData#getTablesreportingTABLE_TYPE = TABLEfor a table with theBigQuery
engine (present insystem.table_enginessince ClickHouse26.8). The engine was missing from the
engine-to-table-type mapping, so it fell back to the defaultTABLE, andgetTables(..., types = {"REMOTE TABLE"})
returned no row for such a table.BigQueryis now mapped toREMOTE TABLE, like the other external-storage engines.
(#3049) -
[jdbc-v2] Fixed
PreparedStatement.getMetaData()losing the result-set schema for a statement whose SQL contains
a comment. TheDESCRIBEquery used to resolve the metadata was built by re-scanning the SQL with a regex that knew
only quoted tokens, so a?inside a--/#//* */comment was rewritten toNULLand an odd'inside a
comment mis-paired the quote alternative, leaving a real placeholder unreplaced — the
DESCRIBEthen failed and the driver silently returned untyped metadata. The metadata query is now built from the
placeholder positions the statement parser already computed, so it always matches the SQL that a parameterized
execution produces. (#3011) -
[client-v2] Fixed reading a
UInt64column into a primitive POJO field (e.g.long) always failing with
ClassCastException: BinaryStreamReader cannot be cast to java.math.BigInteger. The compiled setter for that
combination never emitted a read call, so it cast the reader itself instead of a value and consumed nothing from the
stream, which made every primitive field bound to aUInt64column unusable. The value is now read and converted to
the target primitive with the matchingNumberaccessor, which narrows a value that does not fit the same way a Java
narrowing conversion does (alongholds everyUInt64value bit-for-bit and can be read back with
Long.toUnsignedString(long); abooleanistruefor any non-zero value). Boxed fields (BigInteger,Long) are
unaffected. (#2996) -
[jdbc-v2] Fixed
ResultSetMetaData.getPrecision()andgetScale()returning0for columns wrapped in
SimpleAggregateFunction(func, T). The wrapper is transparent on the read path (values are read as plainT), but
both accessors described the wrapper itself, which carries no precision or scale — so a
SimpleAggregateFunction(sum, Decimal(18, 4))column looked like a scale-0 value and
SimpleAggregateFunction(any, DateTime64(3, tz))looked like second precision. They now describe the nested type.
AggregateFunctioncolumns are unchanged, since their values are aggregation states rather than values of the nested
type. (#3042) -
[clickhouse-jdbc] Fixed
Connection#prepareStatementthrowing aNullPointerExceptionfor an
INSERT ... VALUES (...)statement whose values list the JavaCC parser cannot parse — most commonly one containing a
heredoc string ($$...$$), which the grammar has no token for, but also any other unparsable token inside the list.
The parser's error recovery left the values list's start position recorded without its matching end position, which
was then unboxed unguarded. Both positions are now dropped together, so the driver falls back to its generic
parameter-substitution path instead of failing, and a statement such as
insert into t values ($$a@b$$, ?)is prepared and executed successfully.
(#3033) -
[jdbc-v2] Fixed the ANTLR4 lexer not nesting
/* */block comments. ClickHouse (and the JavaCC parser backend)
raise the nesting level on an inner/*and close the comment only at the matching*/, while the ANTLR4 lexer ended
the comment at the first*/and lexed the rest of it as SQL. With theANTLR4/ANTLR4_PARAMS_PARSERbackends
this made statements the server accepts (e.g.SELECT 1 /* ) /* ) */ ) */, 2) report syntax errors, and made
ANTLR4_PARAMS_PARSERcount a?inside the nested part of a comment as a bind parameter. Comments that do not nest
are unaffected; an unterminated block comment is now skipped to the end of the statement instead of being lexed as
stray tokens. (#3021) -
[client-v2] Fixed reading a
SimpleAggregateFunction(func, T)value held in aDynamiccolumn. The binary type
encoding of such a value (0x2E <function_name> <parameters> <arguments> <argument_type_encodings>) was not consumed
at all, so the read failed withIndexOutOfBoundsException, and the unconsumed encoding bytes would otherwise have
been interpreted as row data and desynchronized the rest of theRowBinarystream. The concrete type is now
reconstructed from the encoding and the value is read as its argument typeT, so it reads exactly like the same
value in a plainSimpleAggregateFunctioncolumn. (#3005) -
[jdbc-v2] Fixed
PreparedStatement#executeBatchsending a syntactically brokenINSERTwhen anANTLR4parser
backend is selected (jdbc_sql_parser=ANTLR4/ANTLR4_PARAMS_PARSER) and the values list contains a value
expression the bundled grammar cannot parse - a JDBC escape sequence ({d '...'}), or valid ClickHouse syntax the
grammar does not cover such as a hex string literal (hex(x'AB')). Such a statement is still given a parse tree,
completed by error recovery, and the values list positions and the value group count were read from it: the values
list was reported to stop at the closing parenthesis of a nested function call, so the batch template lost its own
closing parenthesis, and a two-group values list could be reported as a single group. Both are now discarded when the
statement could not be parsed without errors, so the driver uses its generic parameter substitution path instead -
and, with the betaRowBinarywriter enabled, such a statement is no longer routed to it. The defaultJAVACC
backend is not affected by this. (#3019) -
[jdbc-v2] Fixed the ANTLR4 lexer rejecting
//line comments, which the ClickHouse server and the driver's JavaCC
grammar both accept. Because/is also the division operator,// commentwas lexed as two operator tokens, so a
statement containing a//comment was reported as a syntax error by the ANTLR4-based parser backends (ANTLR4,
ANTLR4_PARAMS_PARSER), and anINSERTpreceded by such a comment was misclassified as a statement with a result
set.//is now skipped like--,#and#!; a single/and//inside a string literal or a quoted identifier
are unaffected. Placeholder counting inside//comments for the backends that scan the raw SQL separately (JAVACC,
ANTLR4) is fixed by
#3009. (#3023) -
[jdbc-v2] Fixed
?parameter placeholders being lost whenjdbc_sql_parser=ANTLR4_PARAMS_PARSERis selected and
the bundled grammar cannot match part of the statement - a JDBC escape sequence ({d '...'}), or valid ClickHouse
syntax the grammar does not cover such as a hex string literal (hex(x'AB')). That backend read the placeholders only
from the parse tree, and the tokens error recovery skips are not part of it, so a placeholder inside such an
expression was dropped:getParameterMetaData().getParameterCount()was too low,setXxxfor a dropped placeholder
failed, and the remaining values were substituted at the wrong offsets. The placeholders are now re-derived from the
original SQL when the statement could not be parsed without errors, as the other two backends always do.
(#3025) -
[client-v2, jdbc-v2] Fixed
Client.getTableSchema(...),Client.getTableSchemaFromQuery(...)andping()
failing against ClickHouse26.8+, where theX-ClickHouse-Formatheader the client sends wins over aFORMAT
clause in the query. These internal queries now set their format in the settings instead of aFORMATclause.
(#3068) -
[jdbc-v2] Fixed an
INSERTwhose values list holds a function call the bundledANTLR4grammar cannot match -
such ashex(x'AB'), valid ClickHouse the grammar has no hex string literal for - being reported to hold no function
call when anANTLR4parser backend is selected (jdbc_sql_parser=ANTLR4/ANTLR4_PARAMS_PARSER). Function calls
in a values list are reported by a callback on the parse tree, and such a statement is still given a parse tree,
completed by error recovery, which skips the tokens the parser recovered on - the function call among them. With the
beta
RowBinarywriter enabled (beta.row_binary_for_simple_insert=true) the statement was then routed to it, where a
literal function-call column cannot be written; it now takes the generic parameter substitution path, as it already
did for a function call the grammar matches. Since such a parse tree cannot tell, any insert that could not be parsed
without errors is now assumed to hold a function call in its values list, so none of them is written with the
RowBinarywriter. The defaultJAVACCbackend is not affected by this.
(#3027) -
[clickhouse-client] Fixed JPMS/module-path service loading for
ClickHouseRequestManagerby loading client
services from thecom.clickhouse.clientmodule, which declares the requiredusesdirectives. This avoids
ServiceConfigurationErrorfailures fromcom.clickhouse.datawhen applications run on the module path.
(#2669) -
[client-v2] Fixed
Client.cancelTransportRequest(queryId)being silently dropped when it landed between two
attempts of a retried operation (query, POJO insert and stream insert): the operation issued the next attempt anyway
and could complete successfully. The request of an attempt now stays registered until the whole operation is over, and
the cancellation is checked before every attempt, so a cancelled operation stops instead of sending another request.
(#2989) -
[client-v1] Fixed
BlockingPipedOutputStream.close()not being idempotent under concurrency: the check of the
closedflag and the closing handshake were not atomic, so two threads closing the same stream (e.g. a writer thread
and a try-with-resources block) could both put the end-of-stream marker into the queue, and the second one failed with
Close stream timed out after <n> msonce the reader had stopped consuming. Exactly one caller now performs the
handshake and runs the post-close action; a concurrent or repeatedclose()returns immediately. A
close()which fails while flushing the remaining data also marks the stream closed and runs the post-close action,
so the stream cannot stay half-closed. (#3055) -
[client-v1] Fixed
NonBlockingPipedOutputStream.close()not being idempotent under concurrency. Two threads could both
flush and mutate the same pending buffer before the reader consumed it, silently replacing the payload with an empty
buffer and running the post-close action twice. Exactly one caller now flushes the pending data, enqueues the
end-of-stream marker, and runs the post-close action; concurrent or repeatedclose()calls return immediately.
(#3057) -
[jdbc-v2] Fixed JDBC escape processing rewriting text inside string literals and quoted identifiers. Because
PreparedStatementinlines bound parameters into the statement text, a bound value containing{fn(or{d '...'}
/{ts '...'}) was re-read as SQL syntax: the{fnwas removed together with the next}found anywhere in the
statement — usually the closing brace of an unrelatedMap/Tupleliteral in another value or row — corrupting the
inserted data or failing with a server-sideSYNTAX_ERROR. Escape sequences are now recognized only outside of quoted
text, and a{fn ...}escape is unwrapped at its matching closing brace, so nested braces (e.g. a{name:Type}query
parameter or a nested escape) stay balanced. (#2995) -
[jdbc-v2] Fixed prepared statements losing parameter markers after an empty
--comment line or after
SELECT * EXCEPT (...), which caused parameter binding to fail withArrayIndexOutOfBoundsExceptionfor the affected
SQL parser backends. (#3052) -
[client-v2] Fixed LZ4 input streams not closing their underlying HTTP response stream. Closing an LZ4 stream
returned byQueryResponse.getInputStream()now releases the wrapped transport stream, including after a partial
read. (#2985) -
[jdbc-v2] Fixed the default JavaCC SQL parser aborting on a heredoc string (
$$body$$,$tag$body$tag$)
whose body contains a character that is not a valid SQL token on its own, such as!,&,|or~. The lexer had
no heredoc token, so such a body raised a lexer error that left the statement classified as
UNKNOWN— an INSERT was reported as a result-set-bearing statement with no table name and no values-list positions,
which disables the batch values template and the table-name based paths. A heredoc is now lexed as a single string
literal. (#3029) -
[client-v2, jdbc-v2] Reduced noisy and potentially sensitive logging; SQL that fails to parse is no longer logged
atWARN(it could contain credentials/PII). (#2970) -
[client-v2] Fixed
BigDecimalvalues written into aDynamiccolumn being silently truncated when the value's
scale exceeded the inferred width's maximum scale, and throwing an overflow error when the value carried an integer
part (e.g.19.99). TheDynamictype inference now sizes theDecimalwidth to hold both the integer digits and
the value's scale, keeps the scale as wide as the width allows without stealing room from the integer part, and writes
the actual column scale into theDynamictype tag. Values that already round-tripped losslessly are unchanged.
(#2966) -
[client-v2] Fixed the
RowBinarywriter throwing
UnsupportedOperationException: Unsupported data type: SimpleAggregateFunctionwhen inserting into a
SimpleAggregateFunction(func, T)column (the reader already supported these columns). The value is now serialized
identically to its underlying typeT, writing theNullablenull-marker byte when the underlying type is nullable
(e.g.SimpleAggregateFunction(anyLast, Nullable(String))), mirroring the read path.
(#2477) -
[client-v2] Fixed the
Dynamictype tag for aSimpleAggregateFunctiontype being written as a bare
0x2Ebyte. The binary type encoding also carries the function name, its parameters and its argument types, so the
server read the function name out of the value bytes that followed and failed with
ATTEMPT_TO_READ_AFTER_EOF. Since the client never infers aSimpleAggregateFunctionfrom a Java value and the
reader cannot read one back out of aDynamiccolumn, this now fails fast with a clear
ClientExceptioninstead of producing a corruptRowBinarystream (the same treatmentQBitalready gets).
(#3007) -
[client-v2, jdbc-v2] Fixed several logging-layer defects. In
client-v2,HttpAPIClientHelper.shouldRetry
threw aClassCastExceptionwhen a retryableServerExceptionwas wrapped as the cause of another exception (the
branch matched on the cause but the cast used the outer exception); the retry decision is now taken from whichever
exception is theServerException. Also inclient-v2, a failure to build the HTTP client version string is now
logged atWARNwith the throwable attached instead of a bareINFOmessage that discarded the cause. Injdbc-v2,
a failure to close the response after a query error now logs the close failure itself instead of the
already-propagated outer exception. (#2968) -
[client-v2] Fixed scalar
Stringquery parameters containing a tab (0x09), newline (0x0a) or backslash being
mishandled through the server'sparam_<name>interface. A{name:String}
parameter value is parsed by the server withdeserializeTextEscaped, which treated a raw tab or newline as a field
delimiter (failing the query withBAD_QUERY_PARAMETER: ... isn't parsed completely)
and a raw backslash as the start of an escape sequence (silently corrupting the value, e.g.C:\temp
becameC:<tab>emp).Client.query(sql, params, ...)now escapes the backslash, tab and newline in a scalarString
parameter so any value round-trips; every other character the server reads verbatim — including the single quote and
carriage return — is left unchanged, soIdentifiervalues and pre-formattedArray/Mapliterals passed as a
Stringstill round-trip. The JDBC driver (jdbc-v2), which inlines parameters as SQL literals and already escaped
the backslash and single quote, is unchanged and covered by a new regression test.
(#2781) -
[client-v2] Fixed a
nullquery-parameter value being sent as the literal string"null", so
Client.query(sql, params, ...)binding a Javanullto a scalar placeholder such as
{x:Nullable(Decimal128(8))}was rejected by the server withBAD_QUERY_PARAMETER
(Value null cannot be parsed as Nullable(...)). A top-level scalarnullis now sent as the ClickHouse
\NNULL sentinel so it binds SQLNULL; anullnested inside anArray/Mapparameter value continues to render
as the SQLNULLkeyword. (#2977) -
[client-v2] Fixed binary array decoding for nullable element types so
Array(Nullable(Float64))and similar
columns now return boxed arrays such asDouble[]instead ofObject[]. This keeps null-supporting arrays aligned
with their element type while preserving the existingObject[]fallback for Variant/Dynamic/Geometry arrays.
(#2846) -
[client-v2] Fixed
Float32/Float64columns throwingClassCastExceptionwhen a value of a non-matching boxed
numeric type was supplied through theObject-typed insert surface — for example a
Double(the natural type of Java literal like1.5) for aFloat32column, or aFloatfor a
Float64column. TheRowBinaryserializer now narrows anyNumber(and, like theInt*columns, aString/
Boolean) throughNumber#floatValue()/Number#doubleValue(), so the float columns accept the same value types the
integer columns already did. (#2930) -
[client-v2] Fixed a
NullPointerExceptionwhen serializing anullvalue into a non-nullable
Enum8/Enum16column.SerializerUtils.serializeEnumDatahad nonullguard, so anullin a non-nullable enum
column reachedvalue.getClass()and failed the RowBinary insert path with a confusing NPE instead of a clear error.
It now throwsIllegalArgumentExceptionnaming the column, consistent with the existingIllegalArgumentException
for other unsupported enum values. Nullable enum columns are unaffected.
(#2931) -
[client-v2] Fixed silent data corruption when serializing a Java
nullinto a non-nullable
Array(...)column viaRowBinaryFormatWriter.RowBinaryFormatSerializer.writeValuePreamble
special-casedArray, emitting a stray marker byte on top of the array length; the server read the extra byte as a
phantom extra row (single-column inserts) or as a column shift that failed the insert withCANNOT_READ_ALL_DATA
(multi-column inserts). A non-nullableArraycannot represent anull, so it now throwsIllegalArgumentException
naming the column — consistent with every other non-nullable type — in both theRowBinaryand
RowBinaryWithDefaultspaths. Empty arrays ([]) still serialize correctly, andDynamiccolumns, which can hold a
nullas the implicitNothingtype, are unaffected. (#2938) -
[client-v2] Fixed POJO insert error classification so transport write failures such as java.net.SocketException:
Broken pipe (Write failed) are now surfaced as transfer/network errors instead of being wrapped as
DataSerializationException. This only changes the exception type reported for request-body transport failures during
Client.insert (...);actual POJO reflection/serialization failures are still reported as DataSerializationException.
(#2729) -
[client-v2] Fixed binary varint decoding for length and count fields so overflowing or overlong values fail with
anIOExceptioninstead of being decoded into corrupted or negativeintvalues.
(#2902) -
[client-v2] Fixed container query parameters being sent unquoted, so
Client.query(sql, params, settings)binding
aList<LocalDate>(or an array/Map) to a placeholder like{ids:Array(Date)}was rejected by the server with
CANNOT_PARSE_INPUT_ASSERTION_FAILED. Parameter values are now formatted by
DataTypeConverter#convertParameterToString(Object)before being sent: pass the raw Java value and the client renders
it into the text the server'sparam_<name>interface expects — aCollection, array (object or primitive), orMap
becomes ClickHouseArray(['2026-05-13']) /Map({'k':'v'}) text withString/temporal leaves single-quoted
and numeric/boolean leaves left unquoted, while a scalar is passed through unquoted as before. No manual
pre-formatting of container parameters is needed. (#2897) -
[client-v2] Fixed
DateTime/DateTime64columns declared with a synthetic fixed-offset timezone name
(Fixed/UTC±HH:MM:SS, e.g.Fixed/UTC+05:30:00) being silently read in UTC instead of the declared offset. The
RowBinaryreader now recovers the offset from the column's declared type.
(#2876) -
[jdbc-v2] Fixed the ANTLR4 SQL parser backends (
jdbc_sql_parser=ANTLR4andANTLR4_PARAMS_PARSER) lexing the
body of a heredoc string ($$body$$,$tag$body$tag$) as ordinary SQL. The lexer had no heredoc token, so every$
was dropped as an unrecognized character and the body was parsed as identifiers, operators and statement separators: a
body that still looked like valid SQL was silently mis-parsed (wrong table name and VALUES-list positions), and a body
containing;— as well as the empty heredoc$$$$— was reported as a parse error, which classifies an INSERT as a
result-set-bearing statement with no values-list positions. A heredoc is now lexed as a single string literal and
accepted as a literal value (INSERT ... VALUESlists, column expressions, settings), and a$inside an identifier
(a$b, or an unterminated tag such as
$foo$bar) is part of the identifier, as the server reads it.
(#3031) -
[jdbc-v2] Fixed the beta RowBinary writer (
DriverProperties.BETA_ROW_BINARY_WRITER) throwing
NoSuchColumnExceptionforINSERTstatements whose column names are backtick-quoted, in particular the canonical
Nestedsub-column wire form`directory`.`id`. The SQL parser now unescapes each backtick-quotedINSERT
column-name component before the by-name server-schema lookup, matching how the table and database identifiers are
already handled. (#2896)
Documentation
- [examples] - New
demo-spring-serviceadded to demonstrate usage of observability SPI.
Dependencies
-
[client-v1,client-v2] Upgraded
org.apache.httpcomponents.client5:httpclient5from5.4.4to5.6.4inclient-v2and
clickhouse-http-clientto pick up the fixes of the newer 5.x releases, including known vulnerabilities.
(#3078) -
[client-v2] Upgraded
org.bouncycastle:bcprov-jdk18onfrom1.84to1.85inclient-v2(#3141).