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

オープン
#3,291 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
35/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
活発
技術スタック
java
領域
databases

調査の方向性

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.

索引モデルが issue の本文から書いたものです。

説明

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

主要言語
Java
スター
1.6k
フォーク
649
平均マージ
1日 20時間
マージ済み PR(30日)
39

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

ClickHouse/clickhouse-java のほかの issue

ClickHouse/clickhouse-java の issue をすべて見る

似ている issue

Java の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。