> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-fix-nav-issues.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> 임베딩에 적합하도록 설계된 ClickHouse native protocol용 헤더 전용 C 클라이언트입니다.

# ClickHouse C 클라이언트

`clickhouse-c`는 ClickHouse [native protocol](/ko/interfaces/tcp)을 위한 헤더 전용 C 클라이언트입니다.
소스 코드와 각 헤더에 대한 참고 문서는 [GitHub 리포지토리](https://github.com/ClickHouse/clickhouse-c)에 있습니다.

상위 수준의 클라이언트와 달리, 이 라이브러리는 의도적으로 많은 기능을 대신 처리하지 않습니다. 핵심 헤더는
사용자가 제공하는 I/O 콜백을 통해 [Native](/ko/interfaces/formats/Native) 형식의 블록을 디코드하고
인코드합니다. 소켓, TLS Context, allocator, 재시도, 연결 풀링은 직접 관리해야 합니다. 따라서
내장하기에 충분히 작습니다. `clickhouse.h`만 포함하면 링크 시점 의존성은 libc 외에 없습니다.

<Note>
  이 라이브러리는 현재 활발히 개발되고 있습니다. v1은 핵심 ClickHouse 타입을 디코드합니다.
  제한 사항이나 누락된 기능은 [issue tracker](https://github.com/ClickHouse/clickhouse-c/issues)를 통해 보고하십시오.
  다만 이 라이브러리는 설계상 일부 기능이 의도적으로 제외되어 있다는 점을 이해하시기 바랍니다.
</Note>

<div id="non-goals">
  ## 라이브러리가 하지 않는 일
</div>

다음은 의도적으로 지원 범위에 포함하지 않은 항목입니다. 이러한 항목은 애플리케이션이나 함께 사용하는 다른 라이브러리에서 처리하십시오:

* HTTP 프로토콜. [HTTP 인터페이스](/ko/interfaces/http)에는 libcurl을 직접 래핑하십시오.
* DNS 확인, 엔드포인트 페일오버, 연결 풀링, retry, 백오프.
* TLS 컨텍스트 수명 주기. OpenSSL 백엔드는 이미 연결된 `SSL`을 사용합니다.
* 스레딩. 각 `chc_client`는 설계상 단일 스레드입니다.
* 라이브러리 내부의 비동기 I/O. 블로킹 클라이언트 호출은 `chc_io.read`를 동기적으로 호출합니다. 자체적으로 I/O를 수행하지 않는
  이벤트 루프 클라이언트가 필요하면 [ioless client](#async-client)를 사용하십시오.

<div id="headers">
  ## 라이브러리 구성 방식
</div>

`clickhouse-c`는 평면적인 헤더 집합 형태로 제공됩니다. 각 헤더에는 선언과 구현이 모두 들어 있으며,
센티널 매크로로 보호됩니다. 빌드에 필요한 헤더를 선택하십시오.

| 헤더                                                                                                               | 용도                                                                    | 링크 플래그           |
| ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ---------------- |
| [`clickhouse.h`](https://github.com/ClickHouse/clickhouse-c/blob/main/doc/clickhouse.md)                         | 코어: 타입, 오류, allocator, I/O vtable, 타입 이름 parser, block reader, writer | —                |
| [`clickhouse-client.h`](https://github.com/ClickHouse/clickhouse-c/blob/main/doc/clickhouse-client.md)           | TCP 패킷 루프: Hello, Query, Data, EndOfStream, Exception, Progress, Pong | —                |
| [`clickhouse-async.h`](https://github.com/ClickHouse/clickhouse-c/blob/main/doc/clickhouse-async.md)             | I/O 없는 클라이언트: 호출자가 바이트를 넘겨 구동하는 동일한 패킷 루프, 소켓 없음                      | —                |
| [`clickhouse-compression.h`](https://github.com/ClickHouse/clickhouse-c/blob/main/doc/clickhouse-compression.md) | compressed-frame layout, CityHash128, 코덱 디스패치, `LZ4`/`ZSTD` 어댑터       | `-llz4 -lzstd`   |
| [`clickhouse-posix-io.h`](https://github.com/ClickHouse/clickhouse-c/blob/main/doc/clickhouse-posix-io.md)       | 블로킹 `read(2)`/`write(2)` 기반 I/O 백엔드                                   | —                |
| [`clickhouse-openssl.h`](https://github.com/ClickHouse/clickhouse-c/blob/main/doc/clickhouse-openssl.md)         | `SSL_read`/`SSL_write` 기반 I/O 백엔드                                     | `-lssl -lcrypto` |

<div id="server-setting">
  ## 필수 서버 설정
</div>

디코더는 wire에서 출력 가능한 타입 이름을 읽으므로, 텍스트로 인코딩되어야 합니다. ClickHouse는
기본적으로 이를 텍스트로 기록하지만, 서버 또는 세션 프로필에서 이를 binary로 설정해도
디코딩이 깨지지 않도록 쿼리에 이 설정을 명시적으로 지정하십시오:

```plaintext theme={null}
output_format_native_encode_types_in_binary_format = 0
```

<div id="adding-to-project">
  ## 프로젝트에 추가하기
</div>

설치할 패키지는 없으므로, `Git submodule` 또는 복사를 사용해 헤더 파일을 프로젝트 트리에 직접 포함해야 합니다.
정확히 하나의 번역 단위만 `CHC_IMPLEMENTATION`을 정의해 구현을 포함하고,
나머지 모든 단위는 선언만 위해 동일한 헤더를 포함합니다.

```c theme={null}
/* clickhouse_impl.c */
#define CHC_IMPLEMENTATION
#include "clickhouse.h"
#include "clickhouse-posix-io.h"
#include "clickhouse-client.h"
#include "clickhouse-compression.h"
```

```c theme={null}
/* every other TU */
#include "clickhouse.h"
#include "clickhouse-client.h"
```

`chc_alloc_stdlib`를 사용하려면 `clickhouse.h`를 포함하기 전에 `CHC_PROVIDE_STDLIB_ALLOC`를 정의하십시오.
`clickhouse-compression.h`에서 lz4/zstd 의존성을 제외하려면 `CHC_NO_LZ4` 또는 `CHC_NO_ZSTD`를 정의하십시오.

<div id="connecting-over-tcp">
  ## TCP를 통한 연결
</div>

ClickHouse 서버와 통신하려면 소켓을 직접 구성하고 이를 `chc_io`로 감싼 후 `chc_client_init`에 전달해야 합니다. 그러면 Hello 핸드셰이크를 동기적으로 수행합니다. 이 라이브러리는 DNS, 페일오버, 재연결, 풀링을 처리하지 않으므로 이러한 부분은 호출자 측에서 처리해야 합니다.

```c theme={null}
int fd = socket(AF_INET, SOCK_STREAM, 0);
int one = 1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof one);

struct sockaddr_in sa = {};
sa.sin_family      = AF_INET;
sa.sin_port        = htons(9000);
sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
connect(fd, (struct sockaddr *) &sa, sizeof sa);

chc_alloc al = chc_alloc_stdlib();
chc_posix_io state;
chc_io io;
chc_posix_io_init(&state, &io, fd, NULL, NULL);

chc_client *client = NULL;
chc_client_opts opts = {
    .user     = "default",
    .password = "",
    .database = "default",
};
chc_err err = {};
if (chc_client_init(&client, &opts, &al, &io, &err) != CHC_OK) {
    fprintf(stderr, "connect: %s\n", err.msg);
    chc_client_close(client);   /* safe to call on the NULL-on-failure handle */
    return 1;
}

const chc_server_info *info = chc_client_server_info(client);
printf("connected to %s %llu.%llu.%llu\n", info->display_name,
       (unsigned long long) info->version_major,
       (unsigned long long) info->version_minor,
       (unsigned long long) info->version_patch);
```

각 `chc_client`는 단일 스레드 방식으로 동작하며 하나의 연결만 래핑합니다. 라이브러리는 `chc_io`
콜백을 동기적으로 호출하며, 해당 콜백이 내부에서 (`epoll`, `io_uring`,
`WaitLatchOrSocket`) 어떻게 처리할지는 사용자 구현에 달려 있습니다.

<div id="running-a-query">
  ## 쿼리 실행
</div>

쿼리를 전송한 다음 `CHC_PKT_END_OF_STREAM`이 나올 때까지 패킷을 계속 비우십시오. `chc_client_send_query_ex`를 사용해
[필수 서버 설정](#server-setting)을 함께 지정하십시오. 기본 `chc_client_send_query`는
빈 설정 목록을 전송하며 서버 기본값을 그대로 따릅니다.

```c theme={null}
chc_query_setting settings[] = {
    { .name = "output_format_native_encode_types_in_binary_format", .value = "0" },
};
chc_query_opts qopts = { .settings = settings, .n_settings = 1 };

const char *sql = "SELECT number, toString(number * number) FROM numbers(5)";
if (chc_client_send_query_ex(client, sql, strlen(sql), &qopts, &err) != CHC_OK) {
    fprintf(stderr, "query: %s\n", err.msg);
    return 1;
}

for (;;) {
    chc_packet pkt = {};
    if (chc_client_recv_packet(client, &pkt, &err) != CHC_OK) {
        fprintf(stderr, "recv: %s\n", err.msg);
        break;
    }

    if (pkt.kind == CHC_PKT_DATA) {
        for (size_t r = 0; r < chc_block_n_rows(pkt.block); r++)
            for (size_t c = 0; c < chc_block_n_columns(pkt.block); c++)
                print_value(chc_block_column_type(pkt.block, c),
                            chc_block_column(pkt.block, c), r);
    } else if (pkt.kind == CHC_PKT_EXCEPTION) {
        fprintf(stderr, "server: %s\n", pkt.exception->display_text);
    }

    bool done = pkt.kind == CHC_PKT_END_OF_STREAM;
    chc_packet_clear(client, &pkt);
    if (done) break;
}
```

서버 예외는 `chc_client_recv_packet`에서 non-OK를 반환하는 형태가 아니라 `CHC_PKT_EXCEPTION` 패킷으로
도착합니다. OK가 아닌 반환은 전송 계층 수준의 실패에서만 발생합니다. 결과의 첫 번째 `CHC_PKT_DATA`
패킷은 스키마를 설명하는 헤더 블록이며 행은 0개이고, 그 뒤에 데이터 블록이 이어집니다.
대신 소유권을 가져오려면 먼저 패킷의 해당 필드를 NULL로 설정해야 하며, `chc_packet_clear`는
패킷의 블록 또는 예외를 해제합니다.

<div id="reading-column-data">
  ## 컬럼 데이터 읽기
</div>

블록은 컬럼 지향입니다. 각 컬럼에는 `chc_column_layout`에서 반환하는 물리적 레이아웃이 있으며,
해당 레이아웃에 따라 분기 처리합니다. 선언된 유형은 `chc_block_column_type`에서 가져옵니다. 복합 레이아웃은 중첩될 수 있으므로,
`Nullable(Array(String))`를 읽으려면 먼저 Nullable을 벗기고, 배열 오프셋을 따라간 다음,
문자열 데이터를 슬라이싱해야 합니다.

| 레이아웃                      | Accessor                                                                                                                   |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `CHC_COL_FIXED`           | `chc_column_fixed_data(c, &elem_size)` — `n_rows * elem_size` 크기의 리틀 엔디언 바이트                                               |
| `CHC_COL_STRING`          | `chc_column_string_data(c)`, `chc_column_string_offsets(c)` — `offsets[i]`는 호스트 바이트 순서에서 행 `i`의 배타적 끝 위치이며, 행 0은 0에서 시작합니다 |
| `CHC_COL_NULLABLE`        | `chc_column_null_map(c)` (행당 1바이트, 1 = `NULL`), `chc_column_nullable_inner(c)`                                             |
| `CHC_COL_ARRAY`           | `chc_column_array_offsets(c)` (누적 끝 위치), `chc_column_array_values(c)`; `Map`은 `Array(Tuple(K, V))`로 디코딩됩니다                 |
| `CHC_COL_TUPLE`           | `chc_column_tuple_arity(c)`, `chc_column_tuple_child(c, i)` — 각 하위 요소의 행 수는 동일합니다                                          |
| `CHC_COL_LOW_CARDINALITY` | `chc_column_lc_key_size(c)` (1/2/4/8), `chc_column_lc_keys(c)`, `chc_column_lc_dict(c)`; 딕셔너리 슬롯 0은 기본값입니다                 |

일반 숫자, 문자열, 널 허용 컬럼을 위한 리더:

```c theme={null}
void print_value(const chc_type *t, const chc_column *c, size_t row)
{
    if (chc_column_layout(c) == CHC_COL_NULLABLE) {
        if (chc_column_null_map(c)[row]) { fputs("\\N", stdout); return; }
        print_value(chc_type_child(t, 0), chc_column_nullable_inner(c), row);
        return;
    }

    switch (chc_column_layout(c)) {
    case CHC_COL_FIXED: {
        /* fixed_data is a raw little-endian byte slab. memcpy into a typed
           local to avoid unaligned loads and strict-aliasing UB, then
           byte-swap on big-endian hosts. */
        size_t es;
        const uint8_t *p = chc_column_fixed_data(c, &es) + row * es;
        switch (chc_type_kind(t)) {
        case CHC_UINT64: { uint64_t v; memcpy(&v, p, sizeof v); printf("%" PRIu64, v); break; }
        case CHC_INT32:  { int32_t  v; memcpy(&v, p, sizeof v); printf("%" PRId32, v); break; }
        case CHC_FLOAT64: { double  v; memcpy(&v, p, sizeof v); printf("%g", v); break; }
        /* ... remaining numeric kinds ... */
        default: break;
        }
        break;
    }
    case CHC_COL_STRING: {
        const uint8_t  *bytes   = chc_column_string_data(c);
        const uint64_t *offsets = chc_column_string_offsets(c);
        uint64_t start = row == 0 ? 0 : offsets[row - 1];
        fwrite(bytes + start, 1, (size_t) (offsets[row] - start), stdout);
        break;
    }
    default: break;
    }
}
```

`CHC_COL_FIXED` 데이터는 wire상에서 리틀 엔디언입니다. 빅 엔디언 호스트에서는 다중 바이트
정수를 직접 바이트 스왑해야 합니다. 오프셋과 LowCardinality 키는 디코딩 시점에 이미 호스트 순서로 스왑됩니다.
UUIDs는 리틀 엔디언 `UInt64` 절반 2개로 구성되며, IPv4는 4바이트 리틀 엔디언 정수이고, IPv6는
network byte order입니다. `DateTime64` 틱은 UTC이며, 타입의 시간대는 메타데이터일 뿐입니다.

신뢰할 수 없는 피어로부터 데이터를 수집할 때는 순회하기 전에 각 컬럼에 대해 `chc_column_validate`를 호출하십시오.
`chc_block_read`는 배열 오프셋 및
LowCardinality 키와 같은 필드 간 불변 조건을 검증하지 않으므로, 그렇지 않으면 위조된 block이 내부 컬럼 경계를 넘어 읽을 수 있습니다.

<div id="inserting-data">
  ## 데이터 삽입
</div>

`chc_block_builder`로 블록을 만든 다음 `chc_client_send_data`에 전달합니다. 빌더는
복사하지 않고 포인터를 기록하므로 컬럼 슬랩은 전송이 끝날 때까지 유효해야 합니다. INSERT는 쿼리를 전송하고,
서버의 헤더 블록을 기다린 다음 하나 이상의 데이터 블록을 전송한 후, 빈 블록을 보내
스트림을 종료합니다.

```c theme={null}
const char *sql = "INSERT INTO greetings (id, message) VALUES";
chc_client_send_query(client, sql, strlen(sql), "", 0, &err);

/* Wait for the server's header block (schema, 0 rows). */
bool got_header = false;
while (!got_header) {
    chc_packet pkt = {};
    if (chc_client_recv_packet(client, &pkt, &err) != CHC_OK) {
        fprintf(stderr, "recv: %s\n", err.msg);
        return 1;
    }
    chc_packet_kind kind = pkt.kind;
    if (kind == CHC_PKT_DATA) got_header = true;
    else if (kind == CHC_PKT_EXCEPTION && pkt.exception)
        fprintf(stderr, "server: %s\n", pkt.exception->display_text);
    chc_packet_clear(client, &pkt);
    if (kind == CHC_PKT_EXCEPTION || kind == CHC_PKT_END_OF_STREAM) return 1;  /* no header coming */
}

chc_block_builder *bb = NULL;
chc_block_builder_init(&bb, &al, &err);

uint64_t ids[3] = { 1, 2, 3 };
chc_type *u64 = NULL;
chc_type_parse("UInt64", 6, &al, &u64, &err);
chc_block_builder_append_fixed(bb, "id", 2, u64, ids, 3, &err);

/* String columns: cumulative exclusive end offsets + a packed byte slab. */
uint64_t offsets[3] = { 5, 11, 20 };   /* "hello", "buenas", "goedendag" */
const uint8_t bytes[] = "hellobuenasgoedendag";
chc_block_builder_append_string(bb, "message", 7, offsets, bytes, 3, &err);

chc_client_send_data(client, bb, &err);   /* the populated block */
chc_client_send_data(client, NULL, &err); /* empty block ends the INSERT */

/* Drain to EndOfStream. */
for (;;) {
    chc_packet pkt = {};
    chc_client_recv_packet(client, &pkt, &err);
    bool done = pkt.kind == CHC_PKT_END_OF_STREAM;
    chc_packet_clear(client, &pkt);
    if (done) break;
}

chc_block_builder_destroy(bb);
chc_type_destroy(u64, &al);
```

`chc_block_builder_append_fixed`는 `n_rows * elem_size`개의 리틀 엔디언 바이트를 받습니다;
`chc_block_builder_append_string`는 패킹된 slab에 대해 호스트 바이트 순서의 누적된 배타적 끝 오프셋을
받습니다. 더 낮은 수준의 `chc_block_write` 대신 `chc_client_send_data`를 통해 빌더를 전달하면
클라이언트가 협상된 revision을 기반으로 블록 옵션을 설정하고 압축을 적용할 수 있습니다.

<div id="compression">
  ## 압축
</div>

`chc_client_opts`에 압축 모드와 초기화된 코덱을 전달합니다. 클라이언트는 들어오는
데이터 패킷의 압축을 해제하고, 나가는 패킷은 압축합니다. 압축 헤더는 `LZ4` 및 `ZSTD` 어댑터를 제공하며,
각 초기화는 자체 슬롯만 채우므로 둘 중 하나라도 지원하려면 둘 다 호출하십시오.

```c theme={null}
#include "clickhouse-compression.h"

chc_codec codec = {};
chc_lz4_codec_init(&codec);
chc_zstd_codec_init(&codec);

chc_client_opts opts = {
    .user        = "default",
    .compression = CHC_COMP_LZ4,   /* or CHC_COMP_ZSTD */
    .codec       = &codec,
};
```

프로젝트에서 바인딩을 제공하지 않는 압축 라이브러리를 사용하려면 `chc_codec`를 직접 채워 넣으십시오;
vtable은 `clickhouse-compression.h`에 선언되어 있습니다.

<div id="tls">
  ## TLS
</div>

`clickhouse-openssl.h`는 `SSL_read`/`SSL_write`용 `chc_io` 백엔드를 제공합니다. OpenSSL은 직접 처리해야 합니다.
라이브러리는 `SSL_CTX`를 생성하거나 인증서를 검증하거나 SNI를 설정하거나 `SSL_connect` /
`SSL_shutdown`을 호출하지 않습니다. `chc_io.read`가 호출될 때는 핸드셰이크가 이미 완료되어 있어야 합니다.

```c theme={null}
#include "clickhouse-openssl.h"

SSL *ssl = /* connected, handshake complete */;
chc_openssl_io state;
chc_io io;
chc_openssl_io_init(&state, &io, ssl, NULL, NULL);
/* hand &io to chc_client_init, same as the POSIX backend */
```

[ClickHouse Cloud](/ko/cloud/overview) 및 기타 TLS가 활성화된 배포에서는 9440
포트에서 네이티브 프로토콜을 사용합니다. 두 백엔드 모두 선택적으로 `check_cancel` 콜백을 허용하며, 이 콜백은 읽기
사이에서 폴링되고, `chc_openssl_io_set_deadline` / `chc_posix_io_set_deadline`을 통해 읽기 마감 시간도 설정할 수 있습니다.

<div id="async-client">
  ## I/O를 사용하지 않는(async) 클라이언트
</div>

`clickhouse-async.h`는 이벤트 루프용 TCP 클라이언트의 I/O 없는 변형입니다. 이 클라이언트는
소켓을 전혀 건드리지 않습니다. 수신한 바이트를 전달하고 전송할 바이트를 꺼내며, `epoll`,
`io_uring`, 또는 `WaitLatchOrSocket`은 직접 구동해야 합니다. 옵션, 패킷 유형, 그리고 블록 빌더는
블로킹 클라이언트와 동일합니다.

`chc_async_client_init`는 I/O를 수행하지 않으며 블로킹되지 않습니다. 이후 핸드셰이크는 재개 가능한
상태 머신으로 실행되며, 모든 송신과 수신도 마찬가지입니다. 파싱이 전달한 바이트 범위를 넘어가면,
호출은 블로킹하는 대신 `CHC_WOULD_BLOCK`을 반환합니다 — 수신 바이트를 더 전달하고 다시
호출하면, 파서는 블록의 중간 지점부터 재개됩니다.

```c theme={null}
#include "clickhouse-async.h"

chc_async_client *c = NULL;
chc_client_opts opts = { .user = "default" };
chc_async_client_init(&c, &opts, &al, &err);

for (;;) {
    int rc = chc_async_handshake(c, &err);
    if (rc == CHC_OK) break;
    if (rc != CHC_WOULD_BLOCK) break;   /* hard error */
    pump(c);   /* drain pending_out to the socket; feed received bytes to chc_async_submit */
}

chc_async_send_query(c, sql, strlen(sql), "", 0, &err);

for (;;) {
    chc_packet pkt = {};
    int rc = chc_async_recv_packet(c, &pkt, &err);
    if (rc == CHC_WOULD_BLOCK) { pump(c); continue; }
    if (rc != CHC_OK) break;

    bool done = pkt.kind == CHC_PKT_END_OF_STREAM;
    if (pkt.kind == CHC_PKT_DATA && pkt.block) { /* read columns as above */ }
    chc_async_packet_clear(c, &pkt);
    if (done) break;
}
```

`pump`는 바이트를 양방향으로 이동합니다. Outbound에서는 `chc_async_pending_out`가 큐에 쌓인 바이트에 대한 포인터와 길이를
돌려줍니다. 소켓이 그중 일부를 받아들인 뒤에는 그 개수만큼 `chc_async_consume_out`를 호출하십시오. 부분 쓰기도
가능합니다. Inbound에서는 소켓 읽기 데이터를 `chc_async_submit`에 전달하십시오. 전송은 블로킹되지 않으며
backpressure도 적용하지 않으므로, pending-out 길이를 주시하고 너무 커지면 추가 전송을 중지하십시오.

작동하는 liburing 드라이버는
[`test/test_async_uring.c`](https://github.com/ClickHouse/clickhouse-c/blob/main/test/test_async_uring.c)에 있습니다.

<div id="allocator">
  ## 메모리와 allocator
</div>

모든 진입점은 `chc_alloc` vtable을 받으므로, 메모리 할당은 호스트에서 사용하는 방식에 따릅니다.

```c theme={null}
typedef struct chc_alloc {
    void *ud;
    void *(*alloc)  (void *ud, size_t bytes);
    void *(*realloc)(void *ud, void *p, size_t old_bytes, size_t new_bytes);
    void  (*free)   (void *ud, void *p, size_t bytes);
} chc_alloc;
```

`clickhouse.h`를 포함하기 전에 `CHC_PROVIDE_STDLIB_ALLOC`를 정의하고, 표준 `malloc` 기반 할당자를 사용하려면
`chc_alloc_stdlib()`를 호출하십시오.

<div id="errors">
  ## 오류 및 서버 예외
</div>

함수는 `CHC_OK` (0) 또는 0이 아닌 `CHC_ERR_*` 코드를 반환합니다. 이 코드는 반환값이며,
호출자 스택에 할당된 `chc_err`에는 사람이 읽을 수 있는 메시지가 저장됩니다. 이 라이브러리는 힙에
오류를 할당하지 않습니다.

```c theme={null}
typedef struct chc_err {
    int  server_code;           /* set when the return code is CHC_ERR_SERVER */
    char msg[CHC_ERR_MSG_LEN];  /* NUL-terminated, default 256 bytes */
    char server_name[64];       /* ClickHouse exception class, if SERVER */
} chc_err;
```

서버 측 쿼리 오류는 `chc_err` 실패가 아닙니다. 이러한 오류는 패킷 스트림에서
`CHC_PKT_EXCEPTION`으로 전달되며, 서버의 `code`, `display_text`, `stack_trace`를 포함합니다. `chc_err`
검사는 전송, 프로토콜, 디코드 실패에만 사용하십시오.

<div id="supported-types">
  ## 지원되는 데이터 타입
</div>

블록 리더는 다음 타입을 디코딩합니다:

* `Int8`–`Int256`, `UInt8`–`UInt256`
* `Float32`, `Float64`, `BFloat16`
* `Bool`
* `Decimal32`, `Decimal64`, `Decimal128`, `Decimal256`
* `Date`, `Date32`, `DateTime`, `DateTime64`, `Time`, `Time64`
* `String`, `FixedString(N)`
* `UUID`, `IPv4`, `IPv6`
* `Enum8`, `Enum16`
* `Nullable(T)`, `Array(T)`, `Tuple(...)`, `Map(K, V)`, `Nested(...)`
* `LowCardinality(T)`
* `Interval`
* `QBit(...)`
* `Point`, `Ring`, `Polygon`, `MultiPolygon`
* `SimpleAggregateFunction(f, T)`, 내부 `T`로 디코딩됩니다
* `JSON` 및 `Object('json')`, 문자열 직렬화에서는 `String` 컬럼으로 디코딩됩니다(아래 참조)

`JSON` 및 `Object('json')`는 쿼리에서 `output_format_native_write_json_as_string=1`을 설정한 경우에만 디코딩됩니다.
각 행은 `CHC_COL_STRING` 컬럼의 JSON 문서 1개로 전달되므로 문자열 Accessor가 이를 읽습니다;
builder는 `chc_block_builder_append_json_string`으로 동일한 shape를 기록합니다. 다른 JSON
serialization version은 모두 해당 setting 이름이 포함된 `CHC_ERR_TYPE`을 반환합니다.

`Variant`, `Dynamic`, `AggregateFunction`은 아직 디코딩할 수 없으며 `CHC_ERR_TYPE`을 반환합니다;
폴백으로 서버 측에서 `String`으로 캐스팅하십시오.
