All notable changes to this project are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
0.5.0 - 2026-08-29
Breaking
ssl: truenow verifies the peer certificate. It previously sentssl: [], which is truthy and so overrode the connection pool's own verifying default, leavingverifyunset —verify_noneon OTP < 26. A connection to a server with a self-signed or otherwise untrusted certificate that used to succeed will now fail during the TLS handshake. That is the point, but it is a visible break. To keep connecting, supply the trust chain explicitly:ssl: [verify: :verify_peer, cacertfile: "/path/to/ca.pem"].insert_stream/3now honours:formatand:chunk_size. Both were previously ignored: every element of the enumerable was sent verbatim asinput_data, so structured rows raisedProtobuf.EncodeErrorand neither option had any effect. Elements that are already binaries are still concatenated verbatim, soFile.stream!/2pipelines are unaffected. If you pass structured rows, make sure the SQLFORMATclause matches:format(which defaults to"TabSeparated") — a mismatch is now a ClickHouse parse error rather than a silent no-op.stream_query/2now emits{:error, reason}instead of ending empty. On a connection failure it previously produced an empty stream, indistinguishable from an empty result set. Consumers that match only{:ok, result}will now raise on the error element instead of silently observing zero rows.
Changed
- Upgraded to gRPC 1.0.
grpc_connection_pool ~> 0.5.2, which bringsgrpc 1.0.4(client/server split into separate packages) andgrpc_core 1.0.4. Huginn's application module no longer startsGRPC.Client.Supervisor: as of grpc 1.0 the:grpcapplication supervises client connections itself, and the old child spec referenced a module that no longer exists, so the application could not boot. - Regenerated the ClickHouse protobuf bindings from ClickHouse
masterwithprotoc-gen-elixir0.17.0.LogsLevelgainsLOG_TEST(mapped to:test), and the generated file now lives atlib/huginn/proto/clickhouse/grpc/clickhouse_grpc.pb.ex. :protobufand:jasonare now declared dependencies. grpc 1.0 no longer depends on:protobuf, and:jasonwas only ever reached transitively despite being used forJSONEachRow.Huginn.Clickhouse.Result.output_formatis nownilrather than""when the server omits it — which it does on every chunk after the first of a streaming response. Code pattern-matching on""needs updating.- Added
Huginn.Clickhouse.Result.from_grpc/2, which accepts:formatand:columnsdefaults so a streaming consumer can decode a later chunk with the metadata established by the first one.from_grpc/1is unchanged.
Fixed
stream_query/2silently dropped most rows of a large result. ClickHouse setsoutput_format/output_columnsonly on the firstResultof a stream; every later chunk was therefore parsed with an unknown format and collapsed into a single opaque row. A 200,000-row query returned 65,412 rows. The format and columns from the first chunk are now carried forward, and the gRPC stream is enumerated exactly once instead of being restarted per chunk.stream_query/2swallowed connection failures, returning an empty stream instead of an{:error, reason}item.insert_stream/3ignored its documented:formatand:chunk_sizeoptions, passing each element straight intoinput_data; structured rows raisedProtobuf.EncodeError. It now encodes and chunks viaHuginn.Clickhouse.Stream.input_stream/3, which also terminates every row with a newline so adjacent chunks cannot merge rows.input_stream/3re-enumerated its source once per chunk (Enum.drop+Enum.any?lookahead), which was quadratic and re-ran side effects for non-restartable sources such asFile.stream!/2.- Retries never fired for an unavailable pool.
:not_connected, the only errorGrpcConnectionPool.get_channel/1returns, was missing from the transient set. - A trailers-only gRPC reply (
grpc-status 0with no message) raised aFunctionClauseErrorinstead of returning{:error, {:unexpected_reply, _}}.
0.4.0 - 2026-06-06
Added
- Telemetry instrumentation.
query/2,insert/3, andinsert_stream/3now emit[:huginn, :query, :start | :stop | :exception]span events with metadata (:method,:sql,:query_id,:pool, and:rows/:statson stop). SeeHuginn.Clickhouse.Telemetry. - Built-in default logger.
Huginn.attach_default_logger/1/detach_default_logger/0attach a ready-madeLoggerhandler for the telemetry events. Off by default. - Opt-in retries.
query/2andinsert/3accept:retriesand:retry_backoffoptions to retry transient transport failures (connection errors, gRPCUNAVAILABLE/DEADLINE_EXCEEDED) with exponential backoff. ClickHouse query errors are never retried. SeeHuginn.Clickhouse.Retry. Huginn.Clickhouse.SQL.escape/1for safe single-quoted string literals.Result.parse_output/3, a column-aware variant ofparse_output/2.
Fixed
- Streaming insert (
insert_stream/3) actually works now. It was calling the client-streaming gRPC stub incorrectly (passing the request enumerable as the options argument), so no data was ever sent. It now opens the stream, pushes eachQueryInfowithGRPC.Stub.send_request/3(settingnext_query_infocorrectly and emitting a final END_STREAM frame), and reads the reply withrecv/2. Verified end-to-end against a live ClickHouse. - Bidirectional streaming (
stream_io/1) actually works now. Same underlying stub-call bug, plus the gRPC stream is now driven from a single owner process (gun delivers all stream messages to one process), so sending and consuming replies no longer dead-locks. Verified end-to-end. - CSV parsing now correctly handles quoted fields containing commas,
embedded quotes (
""), and is symmetric with the library's CSV writer. Previously a naive comma split corrupted such rows. JSONEachRowresults now extract values in column order instead of relying on JSON object key order, which could misalign values with columns.- SQL string escaping in
cancel/2now escapes backslashes before quotes, so aquery_idcontaining a trailing backslash can no longer break out of the string literal. stream_io/1now returns{:error, reason}when a channel cannot be acquired instead of raising aMatchErrorin the caller.
Changed
- Upgraded
grpc_connection_poolto~> 0.4.0(from~> 0.2.1). The 0.4.x line is a rewrite with an ETS/atomics zero-GenServer hot path and pluggable selection strategies;Huginn.Clickhouse.Config.to_pool_config/1'sendpoint:/pool:keyword output remains compatible, so no application changes were required. - Public functions now read the application config once per call instead of twice (no behavior change).
cancel_where/2documents that its condition is interpolated verbatim and must come from trusted input only.
Tooling
- Added GitHub Actions CI (
compile --warnings-as-errors,format,credo,test,dialyzer). - Added a tag-triggered release workflow that publishes to Hex on
v*tags (requires aHEX_API_KEYrepository secret). - Added Credo (
.credo.exs) and Dialyzer (dialyxir) to the toolchain. - Added
:telemetryas a direct dependency; bumpedex_docto~> 0.34. - Added an integration test suite (tagged
:integration, excluded by default; run withmix test --include integrationagainst the docker-compose ClickHouse) covering the request/response and streaming paths end-to-end.
0.3.0 - 2025
- Initial public release on Hex: ClickHouse gRPC client supporting all four
ClickHouse gRPC methods (
ExecuteQuery,ExecuteQueryWithStreamInput,ExecuteQueryWithStreamOutput,ExecuteQueryWithStreamIO), connection pooling, password/JWT auth, query cancellation, and result parsing.