> ## 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.

> عميل C يعتمد فقط على ملفات الترويسة لبروتوكول ClickHouse الأصلي، ومصمم للتضمين.

# عميل ClickHouse بلغة C

`clickhouse-c` هو عميل C يعتمد فقط على ملفات الترويسة لبروتوكول ClickHouse [الأصلي](/docs/ar/concepts/features/interfaces/tcp).
يوجد المصدر والمرجع الخاص بكل ترويسة في [مستودع GitHub](https://github.com/ClickHouse/clickhouse-c).

وعلى خلاف العملاء الأعلى مستوى، فهو يتعمد ألا ينجز الكثير نيابةً عنك. تتولى الترويسة الأساسية فك ترميز كتل تنسيق [Native](/docs/ar/reference/formats/Native) وترميزها
عبر callback للإدخال/الإخراج توفّره أنت. وأنت من يتولى إدارة
`socket` وTLS context ومخصص وretries وتجميع الاتصالات. وهذا ما يجعله صغيرًا بما يكفي
للتضمين: فمجرد تضمين `clickhouse.h` لا يضيف أي dependencies وقت الربط سوى libc.

<Note>
  هذه المكتبة قيد التطوير النشط. يفك الإصدار v1 ترميز أنواع ClickHouse الأساسية.
  أبلغ عن القيود أو الوظائف غير المتوفرة عبر [متعقب المشكلات](https://github.com/ClickHouse/clickhouse-c/issues).
  لكن تجدر الإشارة إلى أن افتقار هذه المكتبة إلى بعض الوظائف هو أمر مقصود في التصميم.
</Note>

<div id="non-goals">
  ## ما لا تفعله المكتبة
</div>

هذه ليست من أهدافها عن قصد. تعامل معها في تطبيقك أو باستخدام مكتبة شقيقة:

* بروتوكول HTTP. استخدم libcurl مباشرةً لتغليف [واجهة HTTP](/docs/ar/concepts/features/interfaces/http).
* حل أسماء DNS، والتبديل التلقائي عند تعطل نقطة النهاية، وتجميع الاتصالات، وإعادة المحاولة، والتراجع التدريجي.
* دورة حياة سياق TLS. تستخدم الواجهة الخلفية لـ OpenSSL كائن `SSL` تكون قد أنشأت اتصالًا به بالفعل.
* تعدد الخيوط. كل `chc_client` أحادي الخيط بحكم التصميم.
* عمليات I/O غير المتزامنة داخل المكتبة. يستدعي العميل الحاجب `chc_io.read` بشكل متزامن. وبالنسبة إلى
  عميل حلقة أحداث لا ينفّذ أي عمليات I/O بنفسه، فاستخدم [عميل ioless](#async-client).

<div id="headers">
  ## كيف تُنَّظم المكتبة
</div>

يأتي `clickhouse-c` كمجموعة مسطحة من ملفات الترويسة. ويضم كل ملف ترويسة التصريحات والتنفيذ معًا،
ويحميه ماكرو حارس. اختر ملفات الترويسة التي تحتاجها عملية البناء لديك.

| الترويسة                                                                                                         | الغرض                                                                                                          | خيارات الربط     |
| ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ---------------- |
| [`clickhouse.h`](https://github.com/ClickHouse/clickhouse-c/blob/main/doc/clickhouse.md)                         | الأساسيات: الأنواع، والأخطاء، والمخصِّص، و`vtable` للإدخال/الإخراج، ومحلّل أسماء الأنواع، وقارئ الكتل، والكاتب | —                |
| [`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: حلقة الحزم نفسها، لكن تُدار عبر تمرير البايتات من المستدعي، من دون socket                         | —                |
| [`clickhouse-compression.h`](https://github.com/ClickHouse/clickhouse-c/blob/main/doc/clickhouse-compression.md) | تخطيط الإطارات المضغوطة، وCityHash128، وتوجيه `codec`، ومهايئات `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)` المحجوبتين                                                | —                |
| [`clickhouse-openssl.h`](https://github.com/ClickHouse/clickhouse-c/blob/main/doc/clickhouse-openssl.md)         | واجهة خلفية للإدخال/الإخراج فوق `SSL_read`/`SSL_write`                                                         | `-lssl -lcrypto` |

<div id="server-setting">
  ## إعداد الخادم المطلوب
</div>

تقرأ وحدة فك الترميز أسماء الأنواع القابلة للطباعة من البيانات المنقولة، لذا يجب ترميزها كنص. يكتب ClickHouse
هذه الأسماء كنص افتراضيًا، ولكن ثبّت هذا الإعداد صراحةً في استعلاماتك حتى لا يتسبب ملف تعريف على مستوى الخادم أو الجلسة،
يضبطه على التنسيق الثنائي، في تعطيل فك الترميز:

```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_PROVIDE_STDLIB_ALLOC` قبل تضمين `clickhouse.h` لاستخدام `chc_alloc_stdlib`.
عرّف `CHC_NO_LZ4` أو `CHC_NO_ZSTD` مع `clickhouse-compression.h` لإزالة الاعتماديات على lz4/zstd.

<div id="connecting-over-tcp">
  ## الاتصال عبر TCP
</div>

للتخاطب مع خادم ClickHouse، عليك إعداد المقبس بنفسك، ثم تغليفه داخل `chc_io` وتمريره إلى
`chc_client_init`، الذي يُجري مصافحة Hello بشكل متزامن. ولا توفّر المكتبة أي حلّ لأسماء DNS،
أو failover، أو إعادة اتصال، أو تجميع الاتصالات — فهذه كلها تقع على عاتق الجهة المستدعية.

```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_PKT_EXCEPTION`، وليس على شكل قيمة إرجاع غير OK من
`chc_client_recv_packet`. ولا تُرجِع قيمة غير OK إلا حالات الإخفاق على مستوى النقل. تكون أول حزمة `CHC_PKT_DATA`
في النتيجة كتلة ترويسة تصف المخطط من دون أي صفوف؛ وتليها كتل البيانات.
تقوم `chc_packet_clear` بتحرير كتلة الحزمة أو الاستثناء — عيّن هذين الحقلين في الحزمة إلى null أولًا
لتتولى الملكية بدلًا من ذلك.

<div id="reading-column-data">
  ## قراءة بيانات الأعمدة
</div>

تكون الكتل موجَّهة بالأعمدة. لكل عمود تخطيط مادي يُعاد بواسطة `chc_column_layout`،
وتُجري `dispatch` بناءً عليه؛ أما نوعه المصرَّح به فيأتي من `chc_block_column_type`. ويمكن أن
تكون التخطيطات المركبة متداخلة، لذا فإن قراءة `Nullable(Array(String))` تعني فكّ `Nullable`،
واجتياز إزاحات المصفوفة، ثم تقطيع بيانات السلسلة النصية.

| Layout                    | Accessors                                                                                                                                         |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `CHC_COL_FIXED`           | `chc_column_fixed_data(c, &elem_size)` — بايتات `little-endian` بعدد `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 = `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 في القاموس هي القيمة الافتراضية                 |

قارئ للأعمدة الرقمية العادية، والسلاسل النصية، والأعمدة `Nullable`:

```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` بتنسيق little-endian في تنسيق النقل؛ وعلى المضيفات ذات ترتيب big-endian، عليك تبديل بايتات الأعداد الصحيحة متعددة البايتات بنفسك. وتكون الإزاحات ومفاتيح LowCardinality قد حُوِّلت بالفعل إلى ترتيب المضيف وقت فك الترميز.
وتتكون معرّفات UUID من نصفين `UInt64` بتنسيق little-endian، وIPv4 هو عدد صحيح little-endian من 4 بايتات، أما IPv6 فهو
بترتيب بايتات الشبكة. وتكون قيم tick في `DateTime64` وفق UTC — والمنطقة الزمنية في النوع ليست سوى بيانات وصفية.

عند إدخال البيانات من نظير غير موثوق، استدعِ `chc_column_validate` على كل عمود قبل المرور
عليه. لا يتحقق `chc_block_read` من الشروط الثابتة بين الحقول، مثل إزاحات المصفوفات
ومفاتيح LowCardinality، لذا قد تتسبب كتلة مزورة، بخلاف ذلك، في القراءة خارج حدود الأعمدة الداخلية.

<div id="inserting-data">
  ## إدراج البيانات
</div>

ابنِ الأعمدة باستخدام الأدوات المساعدة `chc_build_*`، ثم ألحِقها بـ `chc_block_builder`، وبعد ذلك مرّره إلى
`chc_client_send_data`. يستخدم المنشئ مساحة تخزين يوفّرها المستدعي، ويسجّل المؤشرات بدلًا من
نسخها، لذلك يجب أن تظل مساحة التخزين وأشجار الأعمدة والأنواع والأسماء وslabs قائمة طوال مدة الإرسال. ترسل عملية 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` بترتيب little-endian؛ بينما يأخذ `chc_build_string`
إزاحات نهايات تراكمية exclusive بترتيب بايتات المضيف فوق slab packed. تُرجع الدوال المساعدة عُقد الأعمدة
بالقيمة. عشّقها بما يطابق النوع: على سبيل المثال، مرِّر عقدة ثابتة أو نصية إلى
`chc_build_nullable`، ثم مرِّر النتيجة إلى `chc_build_array`، وألحِق جذر المصفوفة. تستخدم أعمدة Tuple،
وLowCardinality، وMap، وGeo البنية الشجرية نفسها: فـ Map هو `Array(Tuple(K, V))`.

يجب أن يكون لجميع الأعمدة في block واحد عدد الصفوف نفسه على المستوى الأعلى. يتحقق الكاتب من الشجرة مقارنةً
بنوع ClickHouse المُحلَّل، لكن يجب على المستدعي تحديد حجم مساحة التخزين `chc_block_col` لكل عملية إلحاق.
يمكنك أيضًا إلحاق عمود مفكوك الترميز من `chc_block_column` مباشرةً لإعادة ترميزه، أو استدعاء
`chc_block_write_cols` باستخدام مصفوفة `chc_block_col` لتجاوز أداة الإنشاء. إن تمرير أداة الإنشاء عبر
`chc_client_send_data` بدلًا من `chc_block_write` منخفض المستوى يتيح للعميل ضبط خيارات block
استنادًا إلى `revision` المتفَق عليه وتطبيق الضغط.

<div id="compression">
  ## الضغط
</div>

مرّر وضع الضغط و`codec` مُهيّأً في `chc_client_opts`. يفكّ العميل ضغط حزم `Data` الواردة
ويضغط الحزم الصادرة. توفّر ترويسة الضغط مواءمي `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` واجهة `chc_io` خلفية فوق `SSL_read`/`SSL_write`. أنت من يتولى إدارة 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/ar/products/cloud/getting-started/intro) وعمليات النشر الأخرى المفعّل فيها TLS البروتوكول الأصلي على
المنفذ 9440. وتقبل كلتا الجهتين الخلفيتين دالة `callback` اختيارية باسم `check_cancel`، ويجري استطلاعها بين عمليات القراءة، كما تقبلان
مهلة للقراءة عبر `chc_openssl_io_set_deadline` / `chc_posix_io_set_deadline`.

<div id="async-client">
  ## عميل Ioless (غير متزامن)
</div>

`clickhouse-async.h` هو إصدار ioless من عميل TCP المخصّص لحلقات الأحداث. وهو لا يتعامل مطلقًا مع
`socket`: فأنت تمرّر إليه البايتات التي استلمتها، وتستخرج البايتات التي يريد إرسالها، بينما تتولى بنفسك تشغيل `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` لديك بنقل البايتات في كلا الاتجاهين. في الاتجاه الصادر، يعيد `chc_async_pending_out` مؤشّرًا وطولًا
إلى البايتات الموجودة في قائمة الانتظار؛ وبعد أن يقبل المقبس جزءًا منها، استدعِ `chc_async_consume_out` بهذا العدد،
ولا بأس بكتابة جزئية. في الاتجاه الوارد، مرّر البيانات المقروءة من المقبس إلى `chc_async_submit`. لا تُحظر عمليات الإرسال
مطلقًا ولا تطبّق ضغطًا عكسيًا، لذا راقب طول البيانات الصادرة المعلّقة وتوقّف عن إصدار عمليات إرسال جديدة عندما
يكبر هذا الطول أكثر من اللازم.

يوجد برنامج تشغيل liburing فعّال في
[`test/test_async_uring.c`](https://github.com/ClickHouse/clickhouse-c/blob/main/test/test_async_uring.c).

<div id="allocator">
  ## الذاكرة والمخصّص
</div>

تستقبل كل نقطة دخول `vtable` لـ `chc_alloc`، لذا يعتمد التخصيص على الآلية التي يستخدمها المضيف.

```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;
```

عرِّف `CHC_PROVIDE_STDLIB_ALLOC` قبل تضمين `clickhouse.h`، واستدعِ `chc_alloc_stdlib()` للحصول على
مُخصِّص قياسي يستند إلى `malloc`.

<div id="errors">
  ## الأخطاء واستثناءات الخادم
</div>

تعيد الدالة `CHC_OK` (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` باستخدام تسلسل String (انظر أدناه)

يُفك ترميز `JSON` و `Object('json')` باستخدام تسلسل String؛ عيّن
`output_format_native_write_json_as_string=1` في الاستعلام. يصل كل صف مدعوم
كمستند JSON واحد في عمود `CHC_COL_STRING`. أنشئ البنية نفسها باستخدام `chc_build_string`;
ويُخرج الكاتب البادئة المطلوبة وفقًا للنوع المُحلَّل.

لا يُفك ترميز `Variant` و `Dynamic` و `AggregateFunction` بعد، وتُرجِع `CHC_ERR_TYPE`;
حوّلها إلى `String` على جانب الخادم كحل احتياطي.
