[client-v2] A Map/List/array bound to a Tuple query parameter is rendered as a Map/Array literal and fails with code 27

Đang mở
#3,291 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
35/100
Loại issue
Lỗi
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
java
Lĩnh vực
databases

Hướng nghiên cứu

Start in client-v2/src/main/java/com/clickhouse/client/api/internal/DataTypeConverter.java, where convertParameterToString is type-blind and convertParameterContainer/pushContainer (around lines 340-396) render Maps and Collections as {...} and [...]. Reproduce with the TestNG snippet in the issue under client-v2/src/test/java/com/clickhouse/client/query/. Done when a Map, List or Object[] bound to {p:Tuple(protocol UInt8, port UInt16)} returns (6,80) and the existing string-binding behaviour is unchanged. The maintainers have not chosen between a type-aware renderer and a tuple-specific overload, so confirm that first.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

Description

Client.query(String, Map<String, Object>, QuerySettings) renders every parameter value through
DataTypeConverter.convertParameterToString, which is type-blind: it never sees the {name:Type}
placeholder's declared type. convertParameterContainer/pushContainer
(client-v2/src/main/java/com/clickhouse/client/api/internal/DataTypeConverter.java:340-396) therefore
renders:

  • any java.util.Map as a ClickHouse Map literal — {'protocol':6,'port':80}
  • any Collection or array as a ClickHouse Array literal — [6,80]

The server's param_<name> reader parses a Tuple(...) value with the tuple text parser, which requires
a leading (. Both renderings are rejected:

Code: 27. DB::Exception: Cannot parse input: expected '(' before: '{\'protocol\':6,\'port\':80}':
value {'protocol':6,'port':80} cannot be parsed as Tuple(protocol UInt8, port UInt16)
for query parameter 'p'. (CANNOT_PARSE_INPUT_ASSERTION_FAILED)

Net effect: there is no natural Java value that can be bound to a Tuple(...) query parameter.
The only thing that works is hand-formatting the literal yourself as a String ("(6,80)"), which
relies on the caller doing the quoting/escaping that the converter exists to do, and which is unsafe
for Tuple(String, ...) elements.

This is the Java-side equivalent of clickhouse-connect issue #1148 (there a dict bound to a named
Tuple is formatted as JSON or as a map literal and hits the same code 27).

ClickHouse server version

26.10.3.7 (official build), reached over HTTP at localhost:8123.

Reproduction

TestNG test placed in client-v2/src/test/java/com/clickhouse/client/query/:

package com.clickhouse.client.query;

import com.clickhouse.client.api.Client;
import com.clickhouse.client.api.query.QueryResponse;
import com.clickhouse.client.api.query.QuerySettings;
import com.clickhouse.data.ClickHouseFormat;
import org.testng.annotations.Test;

import java.io.InputStream;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;

public class TupleParamTest {

    @Test
    public void tupleParameterBinding() throws Exception {
        try (Client client = new Client.Builder()
                .addEndpoint("http://localhost:8123")
                .setUsername("default")
                .setPassword("")
                .build()) {

            Map<String, Object> named = new LinkedHashMap<>();
            named.put("protocol", 6);
            named.put("port", 80);

            run(client, "Map -> named Tuple", named);
            run(client, "List -> named Tuple", Arrays.asList(6, 80));
            run(client, "Object[] -> named Tuple", new Object[]{6, 80});
            run(client, "String \"(6,80)\" -> named Tuple", "(6,80)");
        }
    }

    private static void run(Client client, String label, Object value) {
        Map<String, Object> params = new LinkedHashMap<>();
        params.put("p", value);
        try (QueryResponse resp = client.query(
                "SELECT {p:Tuple(protocol UInt8, port UInt16)} AS t",
                params,
                new QuerySettings().setFormat(ClickHouseFormat.TabSeparated)).get()) {
            InputStream in = resp.getInputStream();
            byte[] buf = new byte[256];
            int n = in.read(buf);
            System.out.println("RESULT " + label + " -> OK: " + new String(buf, 0, Math.max(n, 0)).trim());
        } catch (Exception e) {
            Throwable t = e.getCause() == null ? e : e.getCause();
            String m = t.getMessage();
            System.out.println("RESULT " + label + " -> FAIL: " + (m == null ? t.toString() : m.split("\n")[0]));
        }
    }
}

mvn -pl client-v2 test -Dtest=TupleParamTest

Expected — a Map keyed by the tuple element names, and an ordered List/array, bind the same way
the hand-written (6,80) literal does:

RESULT Map -> named Tuple          -> OK: (6,80)
RESULT List -> named Tuple         -> OK: (6,80)
RESULT Object[] -> named Tuple     -> OK: (6,80)
RESULT String "(6,80)" -> named Tuple -> OK: (6,80)

Actual:

RESULT Map -> named Tuple -> FAIL: Code: 27. DB::Exception: Cannot parse input: expected '(' before: '{\'protocol\':6,\'port\':80}': value {'protocol':6,'port':80} cannot be parsed as Tuple(protocol UInt8, port UInt16) for query parameter 'p'. (CANNOT_PARSE_INPUT_ASSERTION_FAILED)
RESULT List -> named Tuple -> FAIL: Code: 27. DB::Exception: Cannot parse input: expected '(' before: '[6,80]': value [6,80] cannot be parsed as Tuple(protocol UInt8, port UInt16) for query parameter 'p'. (CANNOT_PARSE_INPUT_ASSERTION_FAILED)
RESULT Object[] -> named Tuple -> FAIL: Code: 27. DB::Exception: Cannot parse input: expected '(' before: '[6,80]': value [6,80] cannot be parsed as Tuple(protocol UInt8, port UInt16) for query parameter 'p'. (CANNOT_PARSE_INPUT_ASSERTION_FAILED)
RESULT String "(6,80)" -> named Tuple -> OK: (6,80)

Only the pre-formatted string works.

Suggested fix

In client-v2/src/main/java/com/clickhouse/client/api/internal/DataTypeConverter.java, the parameter
renderer has no notion of a tuple. Two parts:

  1. The param_<name> placeholder type is already present in the SQL text ({p:Tuple(...)}). If the
    converter were given the declared type (as arrayToString(Object, String columnDef) already is), a
    Tuple(...) placeholder could render a Collection/array as (e1,e2,...) and a Map by ordering
    its entries against the declared element names.
  2. Failing that, a dedicated marker/overload for tuple-shaped values would at least give callers a safe
    path that still applies the element quoting/escaping rules in convertParameterContainer.

Note pushContainer would also need the parenthesised form for a tuple nested inside an
Array(Tuple(...)) parameter, which has the same problem today.

Link

Upstream client report: https://github.com/ClickHouse/clickhouse-connect/issues/1148

Ngôn ngữ chính
Java
Star
1.6k
Fork
649
Merge trung bình
1 ngày 20 giờ
Pull request đã merge (30 ngày)
39

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của ClickHouse/clickhouse-java

Tất cả issue của ClickHouse/clickhouse-java

Issue tương tự

Thêm issue về Java

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.