Skip to content

[client-v2] ClickHouseLZ4InputStream "Incomplete read" error drops the byte counts (MessageFormat placeholders passed to a String.format wrapper) #3108

Description

@polyglotAI-bot

Description

ClickHouseLZ4InputStream.readFully(...) builds its truncated-stream error with
MessageFormat-style placeholders, but ClickHouseUtils.format is a wrapper around
String.format(Locale.ROOT, ...), which only understands %s-style conversions.
String.format leaves {0} / {1} untouched and ignores the extra varargs, so the
message reaches the user as the literal template. The two numbers — how many bytes were
actually read and how many were expected — are the entire diagnostic value of the
message, and they are silently dropped.

Low severity (message quality only, no data or control-flow impact), but it makes a
truncated/interrupted compressed response harder to diagnose in exactly the situation
where the numbers matter.

Steps to reproduce

  1. Create a ClickHouseLZ4InputStream over a stream that ends before a full 25-byte LZ4 block header (a truncated or interrupted compressed response).
  2. Call read(byte[], int, int).
  3. Read the message of the resulting IOException.

Error Log or Exception StackTrace

java.io.IOException: Incomplete read: {0} of {1}
	at com.clickhouse.client.api.internal.ClickHouseLZ4InputStream.readFully(ClickHouseLZ4InputStream.java:86)
	at com.clickhouse.client.api.internal.ClickHouseLZ4InputStream.refill(ClickHouseLZ4InputStream.java:101)
	at com.clickhouse.client.api.internal.ClickHouseLZ4InputStream.read(ClickHouseLZ4InputStream.java:61)

Expected Behaviour

The message names the actual counts, e.g. Incomplete read: 10 of 25.

Code Example

Run against main (be331d4):

import com.clickhouse.client.api.internal.ClickHouseLZ4InputStream;
import com.clickhouse.data.ClickHouseUtils;
import net.jpountz.lz4.LZ4Factory;
import java.io.ByteArrayInputStream;
import java.io.IOException;

public class Repro {
    public static void main(String[] args) throws Exception {
        System.out.println(ClickHouseUtils.format("Incomplete read: {0} of {1}", 10, 25));
        System.out.println(ClickHouseUtils.format("Incomplete read: %s of %s", 10, 25));

        byte[] truncated = new byte[10]; // fewer than HEADER_LENGTH (25)
        ClickHouseLZ4InputStream lz4 = new ClickHouseLZ4InputStream(
                new ByteArrayInputStream(truncated),
                LZ4Factory.fastestInstance().fastDecompressor(), 8192);
        try {
            lz4.read(new byte[64], 0, 64);
        } catch (IOException e) {
            System.out.println("IOException message: " + e.getMessage());
        }
    }
}

Observed output:

Incomplete read: {0} of {1}
Incomplete read: 10 of 25
IOException message: Incomplete read: {0} of {1}

The second line shows the same call with %s producing the intended message, so the
wrapper works as documented — only this one call site uses the wrong placeholder syntax.

Root cause

  • client-v2/src/main/java/com/clickhouse/client/api/internal/ClickHouseLZ4InputStream.java:86
throw new IOException(ClickHouseUtils.format("Incomplete read: {0} of {1}", n, len));
  • clickhouse-data/src/main/java/com/clickhouse/data/ClickHouseUtils.java:549-551
public static String format(String template, Object... args) {
    return String.format(Locale.ROOT, template, args);
}

String.format has no meaning for {0}; it copies the braces through verbatim and
never consumes n or len.

Suggested fix

Change the template to the conversion syntax the wrapper actually implements:

throw new IOException(ClickHouseUtils.format("Incomplete read: %s of %s", n, len));

A repo-wide sweep of main sources found this is the only remaining occurrence of
MessageFormat placeholders in a ClickHouseUtils.format / String.format template, so
the change is self-contained. All other ClickHouseUtils.format call sites already use
%s and must keep their current behaviour.

A negative test on the truncated-header path (assert the message contains the real
counts, not the literal template) would prevent the idiom from reappearing.

Configuration

Environment

  • Cloud
  • Client version: main @ be331d4 (VERSION 0.11.0-rc1)
  • Language version: OpenJDK 17
  • OS: Linux (x86_64)

ClickHouse Server

  • ClickHouse Server version: not required — the failure is entirely client-side string formatting and reproduces without a server.

Found by automated analysis of client-v2 while working on an unrelated change, and
verified by executing the real ClickHouseLZ4InputStream.read(...) entry point rather
than by inspection.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions