> ## Documentation Index
> Fetch the complete documentation index at: https://clickhouse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> 내장용으로 설계된 ClickHouse 네이티브 프로토콜용 헤더 전용 C 클라이언트입니다.

# ClickHouse C 클라이언트

`clickhouse-c`는 ClickHouse [네이티브 프로토콜](/docs/ko/concepts/features/interfaces/tcp)을 위한 헤더 전용 C 클라이언트입니다.
소스 코드와 각 헤더별 참고 문서는 [GitHub 리포지토리](https://github.com/ClickHouse/clickhouse-c)에서 확인할 수 있습니다.

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

<Note>
  이 라이브러리는 현재 활발히 개발 중입니다. v1은 핵심 ClickHouse 타입을 디코딩합니다.
  제약 사항이나 누락된 기능은 [issue tracker](https://github.com/ClickHouse/clickhouse-c/issues)를 통해 보고하십시오.
  다만 이 라이브러리에는 설계상 일부 기능이 포함되어 있지 않다는 점을 이해하십시오.
</Note>

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

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

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

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

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

| Header                                                                                                           | Purpose                                                                | Link flags       |
| ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------- |
| [`clickhouse.h`](https://github.com/ClickHouse/clickhouse-c/blob/main/doc/clickhouse.md)                         | 핵심: 타입, 오류, 할당자, 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)             | Ioless client: 호출자가 바이트를 전달해 구동하는 동일한 패킷 루프, socket 없음                 | —                |
| [`clickhouse-compression.h`](https://github.com/ClickHouse/clickhouse-c/blob/main/doc/clickhouse-compression.md) | compressed-frame layout, CityHash128, codec dispatch, `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>

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

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

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

설치할 package는 없으므로, Git submodule이나 복사본을 통해 헤더 파일을 소스 트리에 포함해야 합니다.
정확히 하나의 translation unit에서 `CHC_IMPLEMENTATION`을 정의해 구현을 포함하고;
나머지 모든 unit에서는 선언만을 위해 동일한 헤더 파일을 포함합니다.

```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`를 정의하십시오.
lz4/zstd 의존성을 제거하려면 `clickhouse-compression.h`를 포함하기 전에 `CHC_NO_LZ4` 또는 `CHC_NO_ZSTD`를 정의하십시오.

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

ClickHouse 서버와 통신하려면 소켓을 직접 구성하고, 이를 `chc_io`로 감싼 다음 `chc_client_init`에 전달해야 합니다. `chc_client_init`는 Hello 핸드셰이크를 동기적으로 수행합니다. 이 library는 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)](#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_PKT_EXCEPTION` 패킷으로 전달되며,
`chc_client_recv_packet`에서 non-OK를 반환하는 방식으로 전달되지 않습니다. non-OK가 반환되는 경우는 전송 계층의 실패뿐입니다. 결과의 첫 번째 `CHC_PKT_DATA`
패킷은 0개의 행을 포함한 스키마(schema)를 설명하는 헤더 블록이며, 그 뒤에 데이터 블록이 이어집니다.
`chc_packet_clear`는 패킷의 블록 또는 예외를 해제하므로, 대신 소유권을 넘겨받으려면 먼저 패킷의 해당 필드를 null로 설정하십시오.

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

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

| 레이아웃                      | 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)` — 각 child는 동일한 행 수를 가집니다                                            |
| `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은 기본값입니다                      |

일반 숫자형, 문자열, 널 허용 컬럼을 위한 reader는 다음과 같습니다:

```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는 네트워크 바이트 순서입니다. `DateTime64` 틱은 UTC이며, 유형의 시간대는 메타데이터일 뿐입니다.

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

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

`chc_build_*` helpers로 컬럼을 생성하고, 이를 `chc_block_builder`에 추가한 다음
`chc_client_send_data`에 전달합니다. builder는 호출자가 제공한 스토리지를 사용하며 데이터를 복사하지 않고
포인터만 기록하므로, 스토리지, 컬럼 트리, 타입, 이름, 슬랩은 전송이 완료될 때까지 유지되어야 합니다. 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_col columns[2];
chc_block_builder bb;
chc_block_builder_init(&bb, columns);

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

/* 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_type *string = NULL;
chc_type_parse("String", 6, &al, &string, &err);
chc_column message = chc_build_string(offsets, bytes, 3);
chc_block_builder_append(&bb, "message", 7, string, &message);

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_type_destroy(u64, &al);
chc_type_destroy(string, &al);
```

`chc_build_fixed`는 `n_rows * elem_size`개의 리틀 엔디언 바이트를 사용합니다. `chc_build_string`은 패킹된 슬랩에 대해 host 바이트 순서의 누적
exclusive 종료 오프셋을 사용합니다. helpers는 컬럼 노드를 값으로 반환합니다.
유형에 맞게 이를 중첩하세요. 예를 들어 고정 노드 또는 문자열 노드를 `chc_build_nullable`에 전달하고, 그 결과를 `chc_build_array`에 전달한 다음 배열 루트를 추가합니다. Tuple,
LowCardinality, Map, geo 컬럼은 모두 동일한 트리를 사용합니다. Map은 `Array(Tuple(K, V))`입니다.

블록의 모든 컬럼은 동일한 최상위 행 수를 가져야 합니다. writer는 트리를
파싱된 ClickHouse 유형과 대조하여 검사하지만, 호출자는 각 추가 작업마다 `chc_block_col` 저장소 크기를 맞춰야 합니다.
또한 `chc_block_column`에서 디코딩한 컬럼을 직접 추가해 다시 인코딩할 수도 있고,
builder를 건너뛰고 `chc_block_col` 배열과 함께 `chc_block_write_cols`를 호출할 수도 있습니다. builder를
더 낮은 수준의 `chc_block_write`가 아니라 `chc_client_send_data`를 통해 전달하면 클라이언트가 협상된 revision을 기준으로 블록
옵션을 설정하고 Compression을 적용할 수 있습니다.

<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](/docs/ko/products/cloud/getting-started/intro) 및 TLS가 활성화된 다른 배포는 포트 9440에서 네이티브 프로토콜을
사용합니다. 두 백엔드 모두 선택적 `check_cancel` 콜백을 받을 수 있으며, 이 콜백은 읽기 사이에 폴링되고
`chc_openssl_io_set_deadline` / `chc_posix_io_set_deadline`을 통해 읽기 제한 시간도 설정할 수 있습니다.

<div id="async-client">
  ## Ioless (async) 클라이언트
</div>

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

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

```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`이 큐에 대기 중인 바이트에 대한 포인터와 길이를
반환합니다. socket이 그중 일부를 수락하면 해당 개수를 사용해 `chc_async_consume_out`을 호출하십시오. 부분
쓰기여도 괜찮습니다. Inbound에서는 socket에서 읽은 데이터를 `chc_async_submit`에 전달하십시오. 전송은 절대 block되지 않으며
backpressure도 적용되지 않으므로, pending-out 길이를 주시하고 너무 커지면 더 이상 전송을 요청하지 마십시오.

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

<div id="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` 확인은 전송, protocol, 디코드 실패에만
사용하십시오.

<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 문서로 전달됩니다. `chc_build_string`으로
동일한 형태를 생성하십시오. writer는 파싱된 타입에 필요한 접두사를 출력합니다.

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