Skip to main content
clickhouse-c هو عميل C يعتمد فقط على ملفات الترويسة لبروتوكول ClickHouse الأصلي. يوجد المصدر والمرجع الخاص بكل ترويسة في مستودع GitHub. وعلى خلاف العملاء الأعلى مستوى، فهو يتعمد ألا ينجز الكثير نيابةً عنك. تتولى الترويسة الأساسية فك ترميز كتل تنسيق Native وترميزها عبر callback للإدخال/الإخراج توفّره أنت. وأنت من يتولى إدارة socket وTLS context ومخصص وretries وتجميع الاتصالات. وهذا ما يجعله صغيرًا بما يكفي للتضمين: فمجرد تضمين clickhouse.h لا يضيف أي dependencies وقت الربط سوى libc.
هذه المكتبة قيد التطوير النشط. يفك الإصدار v1 ترميز أنواع ClickHouse الأساسية. أبلغ عن القيود أو الوظائف غير المتوفرة عبر متعقب المشكلات. لكن تجدر الإشارة إلى أن افتقار هذه المكتبة إلى بعض الوظائف هو أمر مقصود في التصميم.

ما لا تفعله المكتبة

هذه ليست من أهدافها عن قصد. تعامل معها في تطبيقك أو باستخدام مكتبة شقيقة:
  • بروتوكول HTTP. استخدم libcurl مباشرةً لتغليف واجهة HTTP.
  • حل أسماء DNS، والتبديل التلقائي عند تعطل نقطة النهاية، وتجميع الاتصالات، وإعادة المحاولة، والتراجع التدريجي.
  • دورة حياة سياق TLS. تستخدم الواجهة الخلفية لـ OpenSSL كائن SSL تكون قد أنشأت اتصالًا به بالفعل.
  • تعدد الخيوط. كل chc_client أحادي الخيط بحكم التصميم.
  • عمليات I/O غير المتزامنة داخل المكتبة. يستدعي العميل الحاجب chc_io.read بشكل متزامن. وبالنسبة إلى عميل حلقة أحداث لا ينفّذ أي عمليات I/O بنفسه، فاستخدم عميل ioless.

كيف تُنَّظم المكتبة

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

إعداد الخادم المطلوب

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

إضافته إلى مشروعك

لا توجد حزمة لتثبيتها، لذا ينبغي لك إدراج ملفات الترويسة ضمن شجرة مشروعك باستخدام Git submodule أو بنسخها مباشرة. تقوم وحدة ترجمة واحدة فقط بتعريف CHC_IMPLEMENTATION وتضمين التنفيذ؛ أما كل الوحدات الأخرى فتضمّن ملفات الترويسة نفسها للتصريحات فقط.
عرّف CHC_PROVIDE_STDLIB_ALLOC قبل تضمين clickhouse.h لاستخدام chc_alloc_stdlib. عرّف CHC_NO_LZ4 أو CHC_NO_ZSTD مع clickhouse-compression.h لإزالة الاعتماديات على lz4/zstd.

الاتصال عبر TCP

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

تشغيل استعلام

أرسل الاستعلام، ثم واصل استقبال الحزم حتى CHC_PKT_END_OF_STREAM. استخدم chc_client_send_query_ex لإرفاق إعداد الخادم المطلوب؛ أما chc_client_send_query وحده فيرسل قائمة إعدادات فارغة ويرث الإعدادات الافتراضية التي يعتمدها الخادم.
تصل استثناءات الخادم على شكل حزم CHC_PKT_EXCEPTION، وليس على شكل قيمة إرجاع غير OK من chc_client_recv_packet. ولا تُرجِع قيمة غير OK إلا حالات الإخفاق على مستوى النقل. تكون أول حزمة CHC_PKT_DATA في النتيجة كتلة ترويسة تصف المخطط من دون أي صفوف؛ وتليها كتل البيانات. تقوم chc_packet_clear بتحرير كتلة الحزمة أو الاستثناء — عيّن هذين الحقلين في الحزمة إلى null أولًا لتتولى الملكية بدلًا من ذلك.

قراءة بيانات الأعمدة

تكون الكتل موجَّهة بالأعمدة. لكل عمود تخطيط مادي يُعاد بواسطة chc_column_layout، وتُجري dispatch بناءً عليه؛ أما نوعه المصرَّح به فيأتي من chc_block_column_type. ويمكن أن تكون التخطيطات المركبة متداخلة، لذا فإن قراءة Nullable(Array(String)) تعني فكّ Nullable، واجتياز إزاحات المصفوفة، ثم تقطيع بيانات السلسلة النصية. قارئ للأعمدة الرقمية العادية، والسلاسل النصية، والأعمدة Nullable:
تكون بيانات 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، لذا قد تتسبب كتلة مزورة، بخلاف ذلك، في القراءة خارج حدود الأعمدة الداخلية.

إدراج البيانات

ابنِ الأعمدة باستخدام الأدوات المساعدة chc_build_*، ثم ألحِقها بـ chc_block_builder، وبعد ذلك مرّره إلى chc_client_send_data. يستخدم المنشئ مساحة تخزين يوفّرها المستدعي، ويسجّل المؤشرات بدلًا من نسخها، لذلك يجب أن تظل مساحة التخزين وأشجار الأعمدة والأنواع والأسماء وslabs قائمة طوال مدة الإرسال. ترسل عملية INSERT الاستعلام، وتنتظر كتلة الترويسة من الخادم، ثم ترسل كتلة بيانات واحدة أو أكثر، ثم ترسل كتلة فارغة لإنهاء الدفق.
يأخذ 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 المتفَق عليه وتطبيق الضغط.

الضغط

مرّر وضع الضغط وcodec مُهيّأً في chc_client_opts. يفكّ العميل ضغط حزم Data الواردة ويضغط الحزم الصادرة. توفّر ترويسة الضغط مواءمي LZ4 وZSTD؛ ولا يملأ كل استدعاء تهيئة إلا خاناته الخاصة، لذا استدعِ كليهما لدعم أيٍّ منهما.
لاستخدام مكتبة ضغط لا يوفّر المشروع لها ربطًا، املأ chc_codec بنفسك؛ ويُصرَّح عن الـ vtable في clickhouse-compression.h.

TLS

يوفّر clickhouse-openssl.h واجهة chc_io خلفية فوق SSL_read/SSL_write. أنت من يتولى إدارة OpenSSL: فالمكتبة لا تُنشئ مطلقًا SSL_CTX، ولا تتحقق من الشهادات، ولا تضبط SNI، ولا تستدعي SSL_connect / SSL_shutdown. وعند استدعاء chc_io.read، يجب أن تكون المصافحة قد اكتملت.
تستخدم ClickHouse Cloud وعمليات النشر الأخرى المفعّل فيها TLS البروتوكول الأصلي على المنفذ 9440. وتقبل كلتا الجهتين الخلفيتين دالة callback اختيارية باسم check_cancel، ويجري استطلاعها بين عمليات القراءة، كما تقبلان مهلة للقراءة عبر chc_openssl_io_set_deadline / chc_posix_io_set_deadline.

عميل Ioless (غير متزامن)

clickhouse-async.h هو إصدار ioless من عميل TCP المخصّص لحلقات الأحداث. وهو لا يتعامل مطلقًا مع socket: فأنت تمرّر إليه البايتات التي استلمتها، وتستخرج البايتات التي يريد إرسالها، بينما تتولى بنفسك تشغيل epoll، وio_uring، أو WaitLatchOrSocket. وتظل الخيارات، وأنواع الحزم، ومُنشئ الكتل، هي نفسها كما في العميل الحاجب. لا يُجري chc_async_client_init أي عمليات I/O ولا يمكن أن يحجب التنفيذ. ثم تُنفَّذ المصافحة بعد ذلك بوصفها آلة حالات قابلة للاستئناف، وكذلك الحال مع كل عملية إرسال واستقبال. وعندما يتجاوز التحليل البايتات التي مرّرتها، يُرجع الاستدعاء CHC_WOULD_BLOCK بدلًا من الحجب — مرّر مزيدًا من البايتات الواردة واستدعِه مرة أخرى، فيستأنف المُحلّل من منتصف الكتلة.
يقوم pump لديك بنقل البايتات في كلا الاتجاهين. في الاتجاه الصادر، يعيد chc_async_pending_out مؤشّرًا وطولًا إلى البايتات الموجودة في قائمة الانتظار؛ وبعد أن يقبل المقبس جزءًا منها، استدعِ chc_async_consume_out بهذا العدد، ولا بأس بكتابة جزئية. في الاتجاه الوارد، مرّر البيانات المقروءة من المقبس إلى chc_async_submit. لا تُحظر عمليات الإرسال مطلقًا ولا تطبّق ضغطًا عكسيًا، لذا راقب طول البيانات الصادرة المعلّقة وتوقّف عن إصدار عمليات إرسال جديدة عندما يكبر هذا الطول أكثر من اللازم. يوجد برنامج تشغيل liburing فعّال في test/test_async_uring.c.

الذاكرة والمخصّص

تستقبل كل نقطة دخول vtable لـ chc_alloc، لذا يعتمد التخصيص على الآلية التي يستخدمها المضيف.
عرِّف CHC_PROVIDE_STDLIB_ALLOC قبل تضمين clickhouse.h، واستدعِ chc_alloc_stdlib() للحصول على مُخصِّص قياسي يستند إلى malloc.

الأخطاء واستثناءات الخادم

تعيد الدالة CHC_OK (0) أو رمز CHC_ERR_* غير صفري. تكون قيمة الإرجاع هي الرمز؛ بينما يحمل chc_err المخصّص على مكدس المستدعي الرسالة المقروءة بشريًا. ولا تُخصِّص المكتبة أي خطأ على كومة الذاكرة مطلقًا.
أخطاء الاستعلامات من جانب الخادم ليست من نوع فشل chc_err. فهي تصل عبر تدفّق الحزم على شكل CHC_PKT_EXCEPTION، وتحمل code وdisplay_text وstack_trace الخاصة بالخادم. اقتصر التحقق من chc_err على إخفاقات النقل والبروتوكول وفك الترميز.

أنواع البيانات المدعومة

يفكّ قارئ الكتل ترميز ما يلي:
  • Int8Int256, UInt8UInt256
  • 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 على جانب الخادم كحل احتياطي.
آخر تعديل في ٢٣ يوليو ٢٠٢٦