Fix client-v2: send null query parameter as \N sentinel - #2981
Fix client-v2: send null query parameter as \N sentinel#2981polyglotAI-bot wants to merge 2 commits into
Conversation
A top-level scalar null query-parameter value was formatted with String.valueOf, producing the literal "null", which the server rejects with BAD_QUERY_PARAMETER for any Nullable(T) placeholder. DataTypeConverter# convertParameterToString now returns the \N NULL sentinel for a top-level null so it binds SQL NULL; nested nulls inside Array/Map values continue to use the SQL NULL keyword. Fixes: #2977
Client V2 CoverageCoverage Report
Class Coverage
|
JDBC V2 CoverageCoverage Report
Class Coverage
|
JDBC V1 CoverageCoverage Report
Class Coverage
|
Client V1 CoverageCoverage Report
Class Coverage
|
…lar-param-encoding
TriageCategory: Summary What this impacts
Concerns
Required reviewer action
|
|



Description
Fixes #2977.
client-v2'sClient.query(sql, params, settings)pre-formats every parameter value withDataTypeConverter#convertParameterToString(Object)before the transport layer sends it as aparam_<name>field. For a top-level scalar the method fell through toString.valueOf(value), so a Javanullwas formatted as the literal four-character string"null". The server then tried to parse"null"against the declaredNullable(T)type and failed the whole query withBAD_QUERY_PARAMETER(Value null cannot be parsed as Nullable(...)), for everyT.The correct wire encoding for a top-level scalar NULL parameter is the
\Nsentinel. Verified against a local server (26.5.1.882):convertParameterToStringnow returns the\Nsentinel for a top-level scalarnull. Anullnested inside anArray/Mapparameter value is untouched — it still renders as the SQLNULLkeyword viaconvertParameterContainer(the server requiresNULL, not\N, inside array/map text literals).The fix lives in the value converter, not the transport layer, per
AGENTS.md("keepHttpAPIClientHelper/ transport code free of ClickHouse-specific formatting; the transport layer should receive already-formatted values").jdbc-v2is unaffected: itsPreparedStatementinlinesnullinto the SQL as the literalNULLand does not use server-side query parameters.Changes
client-v2DataTypeConverter#convertParameterToString: added a top-levelnullguard returning the\NNULL sentinel (newNULL_SCALAR_PARAMconstant); updated the method javadoc.docs/features.md: documented null-parameter encoding as a compatibility-sensitive trait.CHANGELOG.md: added a Bug Fixes entry under0.11.0-rc1.Test
DataTypeConverterTest#testConvertParameterToString(unit): the{null, "\N"}@DataProviderrow now pins the encoding. It fails onmain(expected [\N] but found [null]) and passes with the fix. The existing nested-null rows (['2026-05-13',NULL],[NULL, NULL, NULL]) are the contrast cases — they stayNULLand are unchanged, proving the fix is scoped to the top-level scalar.ParameterizedQueryTest#testNullParam(integration, live server): anullparam bound to{x:Nullable(Int32|String|Decimal128(8)|Date)}now returns SQL NULL (isNull(...) = 1) through the realClient.queryruntime path, instead of throwingBAD_QUERY_PARAMETER. Parametrized via@DataProviderper the repo convention.client-v2DataTypeConverterTest40/40 green;ParameterizedQueryTestintegration 11/11 green (sibling param tests unaffected).changes_checklist.md
String, SQL, or serialized output changed — the
param_<name>text for a scalarnullchanges fromnullto\N. Escaping/quoting verified end-to-end against a live server (not just local string equality); the change is additive (it makes a previously-rejected value work) and does not alter the encoding of any non-null value or of nested nulls.Null handling changed —
null, empty string, and empty collection remain distinct: only a top-level scalarnullmaps to\N; empty containers still render[]/{}and nestednullstill rendersNULL. No public method signature changes.Pre-PR validation gate
main)