Skip to main content
البروتوكول الأصلي هو بروتوكول ثنائي قائم على الاتصال، يتخاطب به عملاء ClickHouse وخوادمه عبر TCP. ويحمل هذا البروتوكول استعلامات SQL، وبيانات النتائج، وحمولات INSERT، وبيانات القياس عن بُعد الخاصة بالتنفيذ، وإشارات الأخطاء. وعليه يعتمد عميل سطر الأوامر، ومشغّل C++‎، ومعظم المشغّلات الأصلية التابعة لجهات خارجية. تتناول هذه الصفحة البروتوكول نفسه: تأطير الحزم، وآلة حالات الاتصال، والتفاوض على الإصدار، ومتن كل رسالة من غير نوع Block. أما البايتات داخل حزم عائلة Data (أي Block وأعمدتها وترميزات كل نوع) فهي مسألة مستقلة، موثّقة في مواصفة صيغة Native.
المواصفة المرافقةهذه الصفحة جزء من مواصفتين متكاملتين، وتُنشر جنبًا إلى جنب مع المواصفة المرافقة صيغة Native. وتتقاسم المواصفتان العمل بحدود واضحة: تختص هذه الصفحة بطبقة الحزم والنقل، بينما تختص مواصفة صيغة Native بالبايتات داخل حزم عائلة Data.
ثمة خصائص تسري على البروتوكول بأكمله. فهو ثنائي وموضعي: لا توجد وسوم حقول إلا داخل BlockInfo، ولذا فإن بايتًا واحدًا في غير موضعه يُخلّ بتزامن كل ما يليه. وهو كذلك بروتوكول ذو حالة، يعالج فيه كل اتصال TCP استعلامًا واحدًا في كل مرة، فلا يدعم تعدد الإرسال. أما الأعداد الصحيحة ثابتة العرض فتُخزَّن بترتيب little-endian.

نظرة عامة

تبدأ كل رسالة في تنسيق النقل برمز نوع الحزمة بصيغة VarUInt، يليه متن تتحدد بنيته وفقًا لهذا الرمز ولإصدار البروتوكول المتفق عليه. يمر الاتصال بثلاث مراحل: مصافحة تُجرى مرة واحدة، ثم أي عدد من عمليات تبادل Ping أو Query، ثم الإغلاق: ينقل بروتوكول TCP الأصلي البيانات الجدولية دائمًا بصيغة Native، بصرف النظر عن أي عبارة FORMAT في استعلام SQL. أما إعادة تحويلها إلى RowBinary أو CSV أو JSON وغيرها فهي مهمة العميل، ويتولاها بعد فك ترميز كتل Native. (أما واجهة HTTP فتعتمد مسار شيفرة مختلفًا يلتزم فعلًا بعبارة FORMAT، إلا أن HTTP يقع خارج نطاق هذا المستند.)

الأمان

أمان النقل (TLS)

يعمل TLS في طبقة النقل، أي في مستوى أدنى من البروتوكول. وعند تفعيله يُشفَّر تدفق TCP بأكمله، وتظل رسائل البروتوكول متطابقة بايتًا ببايت، سواء استُخدم TLS أم لم يُستخدم.

المصادقة

تتم المصادقة أثناء المصافحة، ضمن رسالة ClientHello. ويُرسَل الحقلان user وpassword على هيئة سلاسل نصية بنص صريح، لذا فإن التشفير على مستوى النقل (TLS) هو ما يحمي بيانات الاعتماد أثناء انتقالها عبر الشبكة. يفسّر الخادم الحقل user الفارغ على أنه مستخدم الجلسة الافتراضي المُهيّأ لديه، أي قيمة إعداد الخادم default_session_user (وقيمته الافتراضية default)، ويمكن استبدال هذه القيمة لكل مستمع على حدة في القسم protocols. وإذا هُيّئ الخادم بمستخدم جلسة افتراضي فارغ، أو كان إصداره أقدم من 26.8، فسيُرفض الحقل user الفارغ ويُطلَق استثناء. لاحظ أن clickhouse-client لا يرسل اسم مستخدم فارغًا أبدًا، إذ يستعيض عنه بالقيمة default من جهة العميل. تتوفر مصادقة SSH القائمة على التحدي والاستجابة بدءًا من إصدار البروتوكول 54466 — راجع مصادقة SSH القائمة على التحدي والاستجابة.

السر المشترك بين الخوادم

عند تنفيذ الاستعلامات الموزعة، تتحقق الخوادم من هوية بعضها بعضًا بإثبات معرفتها بـ secret مشترك، دون إرسال الـ secret نفسه في تنسيق النقل. تحمل كل رسالة Query قيمة auth_hash بطول 32 بايت محسوبة بخوارزمية SHA-256 في الحقل 4 من Query. وتُحسب هذه القيمة من ملح (salt) وقيمة nonce والـ secret المُهيأ ونص الاستعلام، ثم يعيد الخادم المستقبِل حسابها ويقارنها بالقيمة المستلمة. ولا تتوفر هذه الآلية إلا عند تفعيل الميزة INTERSERVER_SECRET (v54441). أما العملاء الخارجيون فيرسلون دائمًا سلسلة فارغة في هذا الحقل. راجع المصادقة بين الخوادم.

إدارة الإصدارات وبوابات الميزات

التفاوض على الإصدار

يُعلن كلٌّ من العميل والخادم أثناء المصافحة عن أعلى إصدار من البروتوكول يدعمه. ويكون الإصدار المتفق عليه هو الأدنى بين الإصدارين:
تعتمد كل رسالة تالية على الإصدار المتفق عليه لتحديد الحقول الموجودة في تنسيق النقل.

بوابات الميزات

تُعرَّف كل ميزة بإصدار البروتوكول الذي أُضيفت فيه، وتكون نشطة متى كان الإصدار المتفق عليه أكبر من ذلك الرقم أو مساويًا له.
عندما تكون الميزة نشطة، يجب أن تكون حقولها موجودة في تنسيق النقل. فالبروتوكول يعتمد على ترتيب الحقول اعتمادًا صارمًا، ولذلك فإن حذف أي حقل مرتبط ببوابة ميزة يُفسد تدفق البايتات لجميع الحقول التي تليه.

جدول الميزات

غلاف الحزمة

تتشارك كل رسالة في تنسيق النقل البنيةَ الخارجية نفسها، في كلا الاتجاهين:
تجد الجداول الكاملة لأنواع الحزم في مرجع أنواع الحزم. نوع الحزمة قيمة من النوع VarUInt وليس بايتًا ثابت العرض. ففي القيم الأقل من 128 يُنتج VarUInt البايت المفرد نفسه، لكن يجب على التطبيقات استخدام ترميز VarUInt كي تبقى متوافقة إذا بلغت أنواع الحزم المستقبلية القيمة 128 أو تجاوزتها. لا يوثّق مرجع الرسائل سوى المتن في كل حزمة، أي البايتات التي تلي رمز نوع الحزمة. ويبدأ ترقيم الحقول من 1 عند أول حقل في المتن.

التأطير المجزأ (v54470+)

عند التفاوض على الميزة CHUNKED_PROTOCOL (راجع المصافحة)، تُغلَّف كل حزمة في تنسيق النقل بتأطير مجزأ. ويتم هذا التغليف لكل اتجاه على حدة: إذ يُتفاوَض على اتجاه العميل←الخادم (client→server) واتجاه الخادم←العميل (server→client) كلٌّ على حدة، وقد ينتهي كلٌّ منهما إلى وضع مختلف (مجزأ أو غير مؤطَّر). تخطيط wire لكل حزمة:
تخطيط wire لكل مقطع:
يقع VarUInt الخاص بنوع الحزمة داخل الدفق المقسّم إلى مقاطع: فهو البايت الأول من حمولة الحزمة (البايت الأول من المقطع الأول)، وليس بايتًا منفصلًا يُرسَل قبل التأطير. تتكوّن حمولة المقطع لكل حزمة من [VarUInt packet_type_code][message body] كاملًا كما هو موضح في غلاف الحزمة. وإذا أبقى العميل نوع الحزمة خارج الدفق المقسّم إلى مقاطع، فسيقرأ الطرف الآخر بايت النوع هذا على أنه البايت الأول من حجم المقطع u32، مما يُفقد الاتصال تزامنه. قد تتوزّع الحزمة الواحدة على عدة مقاطع إذا امتلأ المخزن المؤقت لدى الكاتب في منتصف الحزمة، وقد يقع التقسيم في أي موضع، حتى داخل VarUInt الخاص بنوع الحزمة. يدمج القارئ حمولات المقاطع، ويعامل الأصفار الأربعة الختامية (4 بايت) بوصفها حدًّا شفافًا للحزمة، أي أنه يستهلكها دون أن يمرّرها إلى أي جهة تقرأ متن الحزم. تُغلَّف الحزم التي لا متن لها كذلك: فالحزمة المكوّنة من بايت واحد مثل Ping أو Pong تصبح [u32 size = 1][0x04][u32 0] بمجرد الاتفاق على التقسيم إلى مقاطع. وأي وصف لـ “بايت واحد في تنسيق النقل” في موضع آخر من هذه الصفحة يشير إلى الشكل السابق للتقسيم إلى مقاطع. التفاوض. تحمل كلٌّ من ServerHello وAddendum حقلين من النوع String، حقلًا لكل اتجاه، وتُؤخذ قيمهما من {"chunked", "notchunked", "chunked_optional", "notchunked_optional"}:
  • القيمتان chunked / notchunked صارمتان: أي أن الطرف المعني يشترط هذا الوضع تحديدًا.
  • البدائل _optional مرنة: إذ تقبل أي وضع يختاره الطرف الآخر.
تُحسب القيمة المتفق عليها لكل اتجاه بمقارنة تفضيلَي الطرفين: من جهة العميل، يُطابَق تفضيل الإرسال (SEND) لدى العميل مع تفضيل الاستقبال (RECV) لدى الخادم، والعكس صحيح. التوقيت. تنتقل سلاسل التفاوض عبر قناة النقل دون تأطير: ClientHello ← ServerHello (تفضيلات الخادم) ← Addendum (القيم التي تفاوض عليها العميل). ويسري التحوّل إلى التأطير على كل بايت يُرسَل بعد تفريغ Addendum. أما Addendum نفسها وClientHello وServerHello فتُرسَل دائمًا دون تأطير.

دورة حياة الاتصال

يكون الاتصال في أي لحظة في حالة واحدة فقط من أربع حالات: HANDSHAKE أو READY أو READING_RESPONSE أو منتهيًا. ولأن البروتوكول لا يدعم تعدد الإرسال، فإن العميل الذي يرسل طلبًا جديدًا قبل تصريف الاستجابة الواردة السابقة يتسبب في تداخل البايتات في تنسيق النقل، مما يُفسد الدفق.

الحالات

يمتد المسار الناجح في خط مستقيم نحو الأسفل — HANDSHAKE → READY → READING_RESPONSE → READY — مع الحلقة الذاتية Ping/Pong، بينما تصبّ جميع حواف الفشل في المصبّ الوحيد Terminated.

مرحلة المصافحة

تتولى هذه المرحلة المصادقة والتفاوض على إصدار البروتوكول، وتحدث مرة واحدة فقط لكل اتصال، قبل أي عملية أخرى. عند بدء هذه المرحلة، يكون اتصال TCP قد فُتح للتو دون تبادل أي رسائل بعد. وتسير العملية على النحو التالي:
  1. يرسل العميل ClientHello متضمنًا أعلى إصدار يدعمه من البروتوكول.
  2. يقرأ العميل الاستجابة ويوجّهها بحسب نوع الحزمة:
  3. إذا كان negotiated_version ≥ 54458 (ميزة ADDENDUM)، يرسل العميل Addendum. ويستند هذا القرار إلى الإصدار المتفق عليه، لا إلى الإصدار الذي أعلنه العميل.
عند النجاح ينتقل الاتصال إلى الحالة READY؛ وعند حدوث أي error يُنهى الاتصال.

مرحلة Ping

فحصٌ لحيوية الاتصال على مستوى التطبيق، مستقلٌّ عن آلية TCP keepalive. ويؤكد نجاح تبادل Ping/Pong ذهابًا وإيابًا أن اتصال TCP لا يزال قائمًا في كلا الاتجاهين وأن الخادم يستجيب. ورسالة Ping عديمة الحالة (stateless) ولا ترتبط بأي استعلام، ولذلك تكون رسائل Ping المتتالية مستقلةً بعضها عن بعض. بدءًا من الحالة READY، يسير التدفق على النحو التالي:
  1. يرسل العميل Ping.
  2. يقرأ العميل الاستجابة:

مرحلة الاستعلام

يُرسل العميل عبارة SQL، فيبثّ الخادم في المقابل كتل النتائج وبيانات القياس عن بُعد الخاصة بالتنفيذ. وتتألف الاستجابة من سلسلة من الحزم تنتهي بحزمة واحدة فقط، إما EndOfStream أو Exception. بدءًا من الحالة READY، يسير التدفق على النحو التالي: عند حدوث خطأ في أي مرحلة، يرسل الخادم Exception بدلًا من EndOfStream، فيُنهى الاستعلام.
  1. يرسل العميل Query مع query_id فريد (عادةً UUID).
  2. يرسل العميل الجداول الخارجية إن وُجدت، ثم وسم Data الفارغ. تحتوي حزمة Data الفارغة على table_name = "" وnum_columns = 0 وnum_rows = 0. لا يبدأ الخادم تنفيذ الاستعلام إلا بعد استلام هذا الوسم. يمكن تقسيم الجدول الخارجي على عدة حزم Data تحمل table_name نفسه؛ إذ تُثبّت أولى هذه الحزم مخطط الجدول، ويجب أن تحمل كل حزمة لاحقة بالاسم نفسه أسماء الأعمدة وأنواعها ذاتها وبالترتيب ذاته، وإلا رفض الخادم الاستعلام بالخطأ INCORRECT_DATA (راجع Data).
  3. ينتقل العميل إلى الحالة READING_RESPONSE ويُفرغ مخزن الكتابة المؤقت الخاص به.
  4. يقرأ العميل حزم الاستجابة في حلقة، ويعالج كل حزمة بحسب نوعها:
عند استلام EndOfStream أو Exception جرت معالجته، يعود الاتصال إلى READY. أما انتهاك البروتوكول أو خطأ الإدخال/الإخراج فيؤدي إلى إنهاء الاتصال.
كثيرًا ما تقع التطبيقات الجديدة في خطأ بسبب حالة num_rows == 0. فالكتلة الخالية من الصفوف هي وسم حدّ أو ترويسة مخطط، وليست إشارة إلى نهاية الدفق. ولا تنتهي الاستجابة إلا بـ EndOfStream أو Exception.

مرحلة INSERT

مرحلة INSERT هي مرحلة الاستعلام مضافًا إليها تبادلان إضافيان. يرسل العميل عبارة INSERT، فيردّ الخادم بـ كتلة المخطط التي تصف جدول الهدف، ثم يبثّ العميل حزم Data التي تحمل الصفوف، تليها حزمة Data فارغة بوصفها وسمًا للنهاية، وأخيرًا ينهي الخادم التبادل بـ EndOfStream أو Exception. انطلاقًا من الحالة READY، تكون عبارة SQL عبارة INSERT بالصيغة INSERT INTO <table> [(<cols>)] VALUES، دون أي قيمة حرفية مضمّنة من نوع VALUES (...)، لأن بيانات الصفوف تنتقل عبر حزم Data. ويسير التدفق على النحو التالي:
  1. يرسل العميل Query مع تعيين body إلى استعلام INSERT بلغة SQL.
  2. يرسل العميل أي جداول خارجية (وهو أمر نادر مع INSERT). وخلافًا لما يحدث في مرحلة الاستعلام، فإنه لا يرسل هنا وسم Data فارغًا. إذ تُرسَل حزمة Query الخاصة بـ INSERT مع وجود بيانات معلّقة، ولذلك تُؤجَّل الكتلة الفارغة الدالة على نهاية البيانات إلى الخطوة 5؛ فلو أُرسلت قبل كتلة المخطط، لقرأها الخادم على أنها نهاية دفق الصفوف، فيُنهي عملية INSERT دون أي صفوف، ثم يحلّل أول حزمة صفوف فعلية على أنها حزمة شاردة في المستوى الأعلى.
  3. يُصرِّف العميل حزم البيانات الوصفية (TableColumns وProgress وProfileInfo وLog وProfileEvents) إلى أن يقرأ حزمة Data الخاصة بالمخطط، وهي Block تحتوي على 0 صفوف لكنها تتضمن البنية الكاملة للأعمدة (الأسماء والأنواع). وتمثّل كتلة المخطط العقد المُلزِم: إذ يجب أن تطابق الصفوف التي يرسلها العميل بعد ذلك بنية هذه الأعمدة.
  4. يرسل العميل كتلة (أو كتل) البيانات. ولكل كتلة يكتب VarUInt(ClientPacket::Data = 2)، ثم String("") لاسم الجدول الخارجي الفارغ، ثم الـ Block. ويجب أن تتوافق أنواع الأعمدة مع أعمدة كتلة المخطط بحسب الموضع.
  5. يرسل العميل علامة إنهاء المدخلات: حزمة Data تحتوي على Block فارغة (0 أعمدة، 0 صفوف).
  6. يُصرِّف العميل دفق الاستجابة حتى EndOfStream (في حال النجاح) أو Exception (في حال الفشل).
الإدراج غير المتزامن (v54484+). عندما يتضمن الاستعلام async_insert = 1، يضع الخادم الصفوف في قائمة انتظار ثم يُفرّغها ضمن دفعة. وعند الإصدار المتفق عليه ≥ 54484 (PROGRESS_IN_ASYNC_INSERT)، يُرسل الخادم فور اكتمال التفريغ حزمة Progress إضافية، تليها مباشرةً ProfileEvents الخاصة بعملية الإدراج، ثم EndOfStream. أما في الإصدارات الأدنى من 54484 فلا يرسل الخادم حزمة Progress الختامية هذه. وهذه الحزمة هي Progress عادية؛ ولأن الخادم يعيد تعيين مسار تنفيذ الاستعلام قبل احتساب أعداد الكتابة، فإن الزيادة لا تحمل عمليًا سوى الوقت المنقضي، بينما تصل إحصاءات الصفوف والبايتات المكتوبة إلى العميل عبر ProfileEvents المرافقة. ولا يحتاج العميل الذي يُصرِّف أصلًا حزم Progress المتداخلة في الخطوة 6 إلا إلى قبول حزمة إضافية واحدة. يعود الاتصال إلى الحالة READY عند تلقّي EndOfStream أو Exception تمت معالجته. أما انتهاكات البروتوكول وأخطاء الإدخال/الإخراج فتُنهي الاتصال.

مرجع الرسائل

تُسرد الحقول بحسب ترتيب تسلسلها في البيانات المُرسلة. ويستخدم العمود Type القيم التالية:
  • VarUInt — عدد صحيح بلا إشارة متغير الطول (راجع VarUInt).
  • String — بايتات مسبوقة بطولها مُرمَّزًا بصيغة VarUInt (راجع String).
  • UInt8 وInt32 وما شابهها — أعداد صحيحة ثابتة العرض بترتيب little-endian.
  • Bool — بايت واحد قيمته 0x00 أو 0x01.
يبيّن العمود Role الجهة التي تستخدم كل حقل:
  • client — يضبطه العملاء الخارجيون.
  • inter-server — لا يُعتدّ به إلا في الاتصال بين الخوادم، ويكتب فيه العملاء الخارجيون القيمة الافتراضية.
  • universal — يستخدمه الطرفان.
تقتصر هذه الجداول على توثيق متن كل حزمة، أي ما يلي رمز نوع الحزمة.

ClientHello (نوع الحزمة 0)

Client → Server. أول رسالة تُرسَل بعد فتح اتصال TCP.

ServerHello (نوع الحزمة 0)

Server → Client. الرد على ClientHello عند نجاح المصادقة. Rule — عنصر من عناصر password_complexity_rules: تعكس هذه القائمة إعداد سياسة كلمات المرور الذي ضبطه مشغّل الخادم، وهي استرشادية فقط؛ إذ لا يفرض الخادم هذه القواعد أثناء المصافحة. ويمكن للعميل الذي يتيح تغيير كلمة المرور أو تعيينها أن يستخدم هذه القواعد لتنبيه المستخدم إلى الأخطاء قبل إرسال كلمة مرور غير مستوفية للشروط إلى الخادم.
للحدّ من استهلاك الموارد في مواجهة خادم خبيث أو سيئ التهيئة، اجعل الحد الأقصى لقيمة count بعد فك ترميزها 256 مدخلًا، والحد الأقصى لكل من pattern وmessage من النوع String ‏4096 بايت. وتُعد القيمة 0 لـ count (دون أزواج تالية) الحالة الشائعة في الخوادم التي لم تُهيَّأ فيها سياسة لكلمات المرور.

الملحق (Addendum) (بلا نوع حزمة)

Client → Server، ومشروط بـ ADDENDUM (v54458). يُرسَل فور اكتمال تبادل المصافحة. وهو ليس نوع حزمة مستقلًا، إذ تُرسَل الحقول في تنسيق النقل بصيغتها الخام، دون أن يسبقها بايت نوع الحزمة. لا يبدأ العمل بالتأطير المقسَّم إلى مقاطع إلا بعد تفريغ هذا الملحق، أما الملحق نفسه فيُرسَل دون تأطير.

Ping (نوع الحزمة 4)

Client → Server. بلا متن — تتألف الحزمة من بايت واحد 0x04 قبل تطبيق التأطير المجزأ؛ وعند التفاوض على التجزئة يصبح هذا البايت حمولةً من بايت واحد داخل مقطع (انظر التأطير المجزأ).

Pong (نوع الحزمة 4)

Server → Client. بلا متن — تتكوّن الحزمة من بايت واحد 0x04 قبل تطبيق التأطير المجزأ؛ وعند التفاوض على التجزئة يصبح هذا البايت حمولةَ مقطعٍ بطول بايت واحد (راجع التأطير المجزأ).

Exception (نوع الحزمة 2)

من الخادم إلى العميل. تُرسَل عندما يصادف الخادم خطأً في أي مرحلة.

Query (نوع الحزمة 1)

Client → Server.

ClientInfo (مُضمَّن في Query)

Client → Server، مُضمَّن في متن Query (الحقل 2). يتوقف توفّره على CLIENT_INFO (v54032). (يتوقف توفّر بعض الحقول داخل ClientInfo على إصدارات لاحقة، كما هو موضح عند كل حقل أدناه.)
تخطيط يعتمد على الواجهة (الحقول 7–12)تمثّل الحقول 7–12 أعلاه فرع TCP. وعندما لا تكون قيمة query_interface (الحقل 6) هي TCP، تُستبدل هذه الحقول بتخطيط wire مختلف — فالأمر لا يقتصر على حذف حقول اختيارية، ولذلك يجب أن تتفرّع أداة فك الترميز بحسب قيمة الحقل 6.
  • query_interface = 2 (HTTP): تُكتب بدلًا منها معلومات طلب HTTP التي يمرّرها الخادم — http_method (UInt8)، وhttp_user_agent (String)، ثم forwarded_for (String، مشروط بـ X_FORWARDED_FOR_IN_CLIENT_INFO v54443)، وhttp_referer (String، مشروط بـ REFERER_IN_CLIENT_INFO v54447)، وأخيرًا http_handler_name (String) وhttp_request_url (String) (كلاهما مشروط بـ HTTP_HANDLER_IN_CLIENT_INFO v54490، وبهذا الترتيب). ولا تظهر الحقول os_user/client_hostname/client_name/version_*/protocol_version. يحمل الحقلان الأخيران اسم معالج HTTP المطابِق المعرّف بلغة SQL وعنوان URL الخاص بالطلب، كي يُنقلا إلى المقاطع البعيدة (ويكونان فارغين في استعلام HTTP لا يمرّ عبر معالج).
  • أي واجهة أخرى: لا يُكتب أيٌّ من حقول TCP (7–12) ولا أيٌّ من حقول HTTP، ويتابع الدفق مباشرةً بالحقل quota_key.
بعد هذا الفرع يعود التخطيط إلى مساره المشترك: إذ يلي ذلك quota_key (الحقل 13) وdistributed_depth (الحقل 14) في جميع الواجهات، ثم يُكتب version_patch (الحقل 15) في حالة TCP فقط.تبرز أهمية هذا الفرع أساسًا في حركة البيانات بين الخوادم، حين يمرّر الخادم المُبادِر استعلامًا وصل في الأصل عبر HTTP. فأداة فك الترميز التي تقرأ حقول TCP دائمًا ستُخطئ في قراءة هذه الحزم، إذ ستعامل http_method أو http_user_agent على أنه quota_key.
ترميز OpenTelemetry (الحقل 16):

المصادقة بين الخوادم

الحقل 4 في حزمة Query ‏(auth_hash) ليس هو secret الخاص بالعنقود المشترك في تنسيق النقل؛ فإرسال القيمة السرية كما هي سيؤدي إلى فشل المصادقة وتسريب القيمة معًا. وبدلًا من ذلك، يُثبت الخادم الذي يعمل بوصفه عميلًا بين الخوادم أنه يعرف القيمة السرية عبر تجزئة SHA-256 مُملَّحة:
  1. الدخول في وضع الاتصال بين الخوادم. يُعلن الخادم المُتصِل ذلك داخل ClientHello: إذ يحمل الحقل user وسم الاتصال بين الخوادم، ويكون password فارغًا. ثم يُضيف سلسلتين نصيتين أخريين، هما اسم العنقود وقيمة salt بطول 32 بايت تُولَّد حديثًا (encodeSHA256 لقيمة عشوائية)، مباشرةً بعد الحقلين user/password ضمن حزمة ClientHello نفسها. يقرأ الخادم هاتين السلسلتين قبل أن يُرسل ServerHello، لذا يجب على العميل كتابتهما مُسبقًا؛ أما انتظار ServerHello أولًا فيُسبِّب حالة جمود (deadlock)، لأن الخادم يظل متوقفًا في انتظار قراءتهما.
  2. الحصول على القيمة العابرة (nonce). تحمل ServerHello قيمة nonce من النوع UInt64 بطول 8 بايت عند التفاوض على INTERSERVER_SECRET_V2 ‏(v54462).
  3. حساب التجزئة. في كل حزمة Query ليست من نوع InitialQuery، يكتب العميل encodeSHA256(salt + nonce + cluster_secret + query + query_id + initial_user + external_roles + current_roles) في الحقل 4، وهو ملخص (digest) بطول 32 بايت. (تُمثَّل nonce بصيغتها النصية العشرية، ولا تُدرَج إلا عند التفاوض على إصدار ≥ v54462؛ ولا يُضاف external_roles إلا عند التفاوض على INTERSERVER_EXTERNALLY_GRANTED_ROLES ‏(v54472)؛ أما current_roles فهي قائمة أسماء الأدوار المُسلسَلة، أي [VarUInt count][String]*count دون بايت الحضور، ولا تُضاف إلا عند التفاوض على INTERSERVER_CURRENT_ROLES ‏(v54488) وكانت القائمة موجودة.) أما في حالة InitialQuery، أو إذا لم تتم تهيئة secret الخاص بالعنقود، فيكتب العميل سلسلة فارغة بدلًا من ذلك.
  4. التحقق. يقرأ الخادم الحقل 4 بحد أقصى 32 بايت، ثم يُعيد حساب سلسلة الربط نفسها باستخدام نسخته الخاصة من secret الخاص بالعنقود؛ ويُرفَض الاتصال إذا اختلف الملخصان.
أما العملاء الخارجيون (من غير الخوادم) فلا يدخلون هذا الوضع مطلقًا، ويُرسلون دائمًا قيمة auth_hash فارغة.

الإعداد

يُرمَّز مضمَّنًا داخل قائمة الإعدادات في متن Query (حزمة Query، الحقل 3). وتكون القائمة موجودة دائمًا أيًّا كان الإصدار المتفق عليه، وتنتهي بإعداد ذي مفتاح فارغ، أي بقيمة VarUInt 0 واحدة لا تليها أي علامات أو قيمة. ولا يتوقف على الإصدار المتفق عليه سوى ترميز كل إعداد على حدة، ويتحكم في ذلك SETTINGS_SERIALIZED_AS_STRINGS (v54429). v54429+ (STRINGS_WITH_FLAGS): يتألف كل إعداد من الثلاثية الموضحة أدناه: لا يُرسَل الحقلان 2 و3 عندما يكون key فارغًا. ما قبل 54429 (BINARY): يأتي كل إعداد على الصورة [String key][type-specific binary value]، إذ لا يُكتب الحقل flags، وتُرمَّز القيمة بالصيغة الثنائية الأصلية للإعداد (كعدد صحيح ثابت العرض أو string مسبوق بطوله مثلًا) بدلًا من string عشري أو نصي. وتنتهي القائمة كذلك بـ key فارغ. ويجب على العميل الذي يستهدف إصدارًا متفقًا عليه أقل من 54429 أن يقرأ هذه الصيغة الثنائية ويكتبها، لا الثلاثية المذكورة أعلاه. (ويُستثنى من ذلك الإعدادات المخصصة التي يعرّفها المستخدم، إذ تحمل دائمًا flags وقيمة من نوع string في كلا الترميزين.) يجمع الحقل flags ما يلي:
  • 0x01: Important: يؤثر الإعداد في نتائج الاستعلام، ويجب ألا تتجاهله الأطراف الأقدم دون تنبيه.
  • 0x02: Custom: إعداد مخصص يعرّفه المستخدم.
  • 0x0c: حقل مستوى من بتّين، وليس علامة مستقلة: 0x00 = Production، و0x04 = Obsolete، و0x08 = Experimental، و0x0c = Beta. اقرأ البتّين معًا (flags & 0x0c)، فالاكتفاء بالاختبار flags & 0x04 سيصنّف Beta (0x0c) خطأً على أنه Obsolete.
  • 0x80: HotReload (إعادة تحميل الإعداد دون إعادة التشغيل؛ مُعرَّف في enum العلامات، ويَرِد غالبًا في إعدادات التنسيق).

المعلمة

معلمات الاستعلام، وتُستخدم في الاستعلامات ذات المعلمات مثل SELECT {x:UInt64}. تُرمَّز بالطريقة نفسها التي يُرمَّز بها الإعداد مع تعيين العلامة Custom (0x02)، وتُنهى كذلك بمفتاح فارغ.
قيمة المعلمة هي تمثيل SQL للقيمة، وليست قيمة حرفية خامًا. يجب تمرير المعلمات من النوع String محاطةً مسبقًا بعلامات اقتباس مفردة (فمثلًا، قيمة {name:String} هي 'Alice' وليست Alice)، وإلا فسيرفضها محلل القيم في الخادم.

Data (نوع الحزمة 1 خادم→عميل، نوع الحزمة 2 عميل→خادم)

تُستخدم في كلا الاتجاهين، وتحمل كتل النتائج، وبيانات INSERT، والجداول الخارجية، ووسوم نهاية البيانات. تنسيق wire متماثل في الاتجاهين، إذ يتضمن كلاهما بادئة table_name قبل الـ Block، ولا يختلفان إلا في بايت نوع الحزمة.
وسم نهاية البيانات هو حزمة كتلتها فارغة، أي تحتوي على 0 أعمدة و0 صفوف، بصرف النظر عن قيمة table_name. ولا يعدّ الخادم حزمة Data المرسلة من العميل حزمةَ إنهاء إلا إذا كانت الكتلة المفككة الترميز فارغة (block.empty())؛ أما الحزمة التي تحمل table_name = "" مع كتلة غير فارغة فهي حزمة صفوف عادية وليست حزمة إنهاء. وعليه، يتألف دفق صفوف INSERT من سلسلة من كتل Data غير الفارغة، تليها كتلة Data فارغة واحدة تُنهيه. تجد بدائل الكتلة ومعانيها موثّقة في قسم بدائل الكتلة.

Progress (نوع الحزمة 3)

Server → Client. تُرسَل بشكل دوري أثناء تنفيذ الاستعلام. جميع الحقول من النوع VarUInt، وتحمل كل حزمة الزيادات منذ حزمة Progress السابقة، لا الإجماليات التراكمية. فقبل الإرسال، يقرأ الخادم عدّاداته ويعيد ضبطها إلى الصفر بشكل ذري، ثم يحسب elapsed_ns على أنه الفارق الزمني منذ آخر إرسال. لذلك يجب على العميل تجميع الحزم المتتالية محليًا للحصول على الإجماليات الجارية؛ فالتعامل مع الحزمة على أنها قيمة مطلقة يجعل مؤشر التقدم يتراجع إلى الخلف أو يُظهر عددًا أقل من الفعلي بمجرد وصول أكثر من حزمة واحدة.

ProfileInfo (نوع الحزمة 6)

Server → Client. تُرسَل مرة واحدة لكل استعلام، قرب نهاية التنفيذ.

Totals (نوع الحزمة 7)

Server → Client. تُرسَل مع الاستعلامات التي تتضمن WITH TOTALS. وتنسيق wire الخاص بها مطابق تمامًا لتنسيق Data: قيمة string باسم table_name (فارغة دائمًا) يليها Block، ولا يختلف عنها إلا في بايت نوع الحزمة.

Extremes (نوع الحزمة 8)

Server → Client. تُرسَل عند تفعيل الإعداد extremes. تنسيق wire مطابق لتنسيق Data. تحتوي الكتلة على صفّين بالضبط: يتضمن الصف 0 القيمة الدنيا لكل عمود، ويتضمن الصف 1 القيمة القصوى.

Log (نوع الحزمة 10)

Server → Client. تُرسَل عندما تكون للاستعلام قائمة انتظار سجلات نشطة (عبر الإعداد send_logs_level؛ راجع log streaming). تستخدم الغلاف نفسه وتنسيق المحتوى نفسه المستخدمَين في Data. تحتوي الكتلة على قيمة ثابتة num_columns = 8 وعلى مخطط محدد مسبقًا. يمثّل كل سطر سجل صفًا واحدًا يمتد عبر الأعمدة الثمانية جميعها، وقد تحمل حزمة Log واحدة صفوفًا عديدة.
الأعمدة الثمانية، بهذا الترتيب تحديدًا:

ProfileEvents (نوع الحزمة 14)

Server → Client. تحمل عدّادات الأداء الخاصة بكل استعلام. تستخدم الغلاف وتنسيق المحتوى نفسيهما المستخدمَين في Data. تحتوي الكتلة على عدد ثابت من الأعمدة num_columns = 6 وعلى مخطط محدد مسبقًا، ويمثّل كل حدث صفًا واحدًا.
الأعمدة الستة:
نوع العنصر في العمود value ليس ثابتًا في جميع الحزم، إذ تُرسل الخوادم الأقدم UInt64، بينما تُرسل الخوادم الأحدث Int64. لذا اقرأ سلسلة نوع العمود من ترويسة الكتلة بدلًا من افتراض عرض محدد.

TableColumns (نوع الحزمة 11)

Server → Client، ومشروط بـ COLUMN_DEFAULTS_METADATA (v54410). يرسل الخادم هذه الحزمة قبل كتلة المخطط الخاصة بعملية INSERT لنقل البيانات الوصفية للقيم الافتراضية للأعمدة، ولكن فقط عندما يكون الإصدار المتفق عليه ≥ 54410 و يكون الإعداد input_format_defaults_for_omitted_fields مفعّلاً. في الإصدارات الأقل من 54410 لا تُرسَل الحزمة أبداً، لذا يجب على العميل الأقدم ألا ينتظرها، إذ تصل كتلة المخطط Data مباشرةً. أما العميل ذو الإصدار v54410 فما فوق فيجب أن يكون مستعداً لكلا الاحتمالين: حزمة TableColumns اختيارية تليها كتلة المخطط.
متن مضغوط في الإصدار v54481 فما فوقعند الإصدار المتفق عليه ≥ 54481 (COMPRESSED_LOGS_PROFILE_EVENTS_COLUMNS) يكتب الخادم كلا الحقلين عبر مسار الإخراج نفسه القابل للضغط اختيارياً، لذا عندما يتضمن الاستعلام compression = true يقع متن TableColumns بأكمله (external_table + columns_description) داخل إطار الضغط، ويقرؤه العميل عبر الدفق المقابل بعد فك ضغطه. أما عندما لا يستخدم الاستعلام الضغط، فيُنقل المتن في تنسيق النقل غير مضغوط تماماً كما يوضح الجدول أعلاه. وتبرز أهمية ذلك في استجابات مخطط INSERT: فالعميل الذي يغيّر طريقة معالجة الضغط لحزمتي Log وProfileEvents دون TableColumns سيخطئ في قراءة الاستجابة عند تفعيل ضغط الاستعلام.

TimezoneUpdate (نوع الحزمة 17)

Server → Client، ومشروطة بـ TIMEZONE_UPDATES (v54464). تُرسَل في موضع واحد فقط: مُهيِّئ دالة الجدول input (أي استعلام بالصيغة INSERT INTO <table> SELECT ... FROM input('<structure>')، الذي تتدفق فيه الصفوف من العميل). فور إرسال الخادم كتلة Data الخاصة بمخطط الإدخال (راجع مرحلة INSERT)، يُرسل الخادم TimezoneUpdate حاملةً القيمة الحالية لـ session_timezone في سياق الاستعلام، كي يحلّل العميل الصفوف التي يوشك على إرسالها وفق المنطقة الزمنية نفسها. ولا يُرسل الخادم هذه الحزمة مطلقاً عند أي تغيير لـ SET session_timezone في أثناء تنفيذ الاستعلام، ولا لإبلاغ العميل بكيفية تنسيق كتل النتائج اللاحقة. تصل الحزمة مرة واحدة فقط، مباشرةً بعد كتلة مخطط الإدخال وقبل أن يبدأ العميل في إرسال كتل الصفوف. ويجب على أداة فك الترميز التي تتجاهل TimezoneUpdate أن تقرأ قيمة String التي تليها رغم ذلك، حفاظاً على محاذاة البيانات المتسلسلة.

مصادقة SSH بآلية التحدي والاستجابة (أنواع الحزم 11 و12 و18)

تتوقف هذه الآلية على SSH_AUTHENTICATION (v54466)، ولا تُفعَّل إلا عند طلبها صراحةً. يدخل الاتصال في مسار SSH عندما ترسل ClientHello القيمة user = " SSH KEY AUTHENTICATION " + <real_user> (مع المسافتين في البداية والنهاية) والقيمة password = "". يقرأ الخادم البادئة، ثم يزيلها لاستعادة اسم المستخدم الحقيقي، وينتقل إلى آلية التحدي والاستجابة. يحلّ هذا المسار محل المصادقة بكلمة المرور، ويجري تبادل التحدي والاستجابة قبل ServerHello، إذ يؤجّل الخادم ردّ Hello إلى أن تنجح المصادقة:
  1. يرسل العميل ClientHello مع بادئة وسم SSH وكلمة مرور فارغة.
  2. يرسل العميل SSHChallengeRequest (الحزمة 11). لم يُرسل الخادم ServerHello بعد، فهو يعالج المصادقة أولًا ويبقى متوقفًا هنا بانتظار هذه الحزمة.
  3. يردّ الخادم بالحزمة SSHChallenge التي تحمل بايتات عشوائية (الحزمة 18).
  4. يبني العميل السلسلة المراد توقيعها ويوقّع عليها لا على التحدي الخام، ثم يرسل SSHChallengeResponse (الحزمة 12) مرفقةً بالتوقيع. الرسالة الموقَّعة هي ناتج دمج أربعة أجزاء بايتًا ببايت، دون أي فواصل، وبهذا الترتيب تحديدًا:
  5. يتحقق الخادم من التوقيع باستخدام المفتاح العام المسجّل للمستخدم، بعد أن يعيد بناء السلسلة نفسها decimal(protocol_version) + default_database + user + challenge. عند النجاح يرسل ServerHello، وهو الرد نفسه المتّبع في مسار كلمة المرور، وتمضي المصافحة على نحو طبيعي (Addendum وغيرها)؛ أما عند الفشل فيُعيد Exception ويُنهي الاتصال. وأي عميل يكتفي بتوقيع بايتات التحدي الخام ستفشل مصادقته.
هذا عكس ما يحدث في مصافحة كلمة المرور، حيث تأتي رسالة ServerHello مباشرةً بعد رسالة ClientHello. أما عند استخدام SSH auth، فيُحجَب ServerHello إلى أن يتم التحقق من التوقيع، ولذلك تُدمَج آلية SSH challenge-response ضمن المصافحة قبل ظهور أي ServerHello.
لا يرى العملاء الخارجيون الذين لا يستخدمون SSH auth الحزم 11 أو 12 أو 18 إطلاقًا، إذ لا تُرسَل هذه الحزم عبر الاتصال ما لم يختر المستخدم تفعيلها صراحةً من خلال البادئة في اسم المستخدم.

MergeTreeAllRangesAnnouncementResponse (نوع الحزمة 14)

من العميل إلى الخادم، للتواصل بين الخوادم فقط. مشروط بـ parallel_replicas_protocol_version ≥ 8 (راجع VERSIONED_PARALLEL_REPLICAS_PROTOCOL). لا يرسل العملاء الخارجيون هذه الحزمة إطلاقًا. عندما يكون إصدار parallel-replicas المتفاوض عليه ≥ 8، تتغير دورة الطلب/الاستجابة لدى العقدة البادئة (initiator) في التعامل مع حزمة MergeTreeAllRangesAnnouncement الواردة من العقدة التابعة (follower) (نوع الحزمة 15، في اتجاه الخادم←العميل):
  1. تفتح العقدة التابعة مسار تنفيذ القراءة الخاص بها، ثم ترسل MergeTreeAllRangesAnnouncement إلى العقدة البادئة.
  2. لا ترد العقدة البادئة بـ MergeTreeAllRangesAnnouncementResponse إلا إذا كانت قيمة mode في الإعلان غير Default (أي WithOrder = 1 أو ReverseOrder = 2، وكلتاهما تُستخدمان في عمليات القراءة المتوازية المرتّبة). أما إذا كانت mode = Default = 0 فلا ترد العقدة البادئة، ولا تنتظر العقدة التابعة أي رد، لأن الوضع Default يوزّع النطاقات مع كل MergeTreeReadTaskRequest ولا يحتاج إلى قائمة أجزاء البيانات مسبقًا.
  3. تنتظر العقدة التابعة الاستجابة (متى كانت متوقعة) قبل إرسال أول MergeTreeReadTaskRequest (حزمة الخادم 16، تُرسل من العقدة التابعة إلى العقدة البادئة، فترد الأخيرة بـ MergeTreeReadTaskResponse، حزمة العميل 10)، وتستخدم قائمة أجزاء البيانات المُعادة لقصر إنشاء المصادر على أجزاء البيانات التي يملكها الدفق #split_i الخاص بها دون غيرها.
في الإصدارات الأقدم من 8، يكون الإعلان من نوع fire-and-forget (يُرسَل دون انتظار تأكيد) أيًّا كان الوضع، وتُنشئ العقدة التابعة المصادر على جميع أجزاء البيانات المعروفة محليًا (السلوك القديم).

المتن

متن RangesInDataPartsDescription

متن RangesInDataPartDescription

متن MergeTreePartInfo

متن MarkRanges

مرجع أنواع الحزم

Client → Server

من الخادم إلى العميل

QueryPlan

QueryPlan هي حزمة عميل مخصصة للاتصال بين الخوادم فقط. تأتي بعد حزمة Query تكون قيمة stage فيها QueryPlan (7)، وتحتوي على دفق واحد لخطة مُسلسَلة. قبل الإصدار 10، لا يتضمن التنسيق الحقلين 2 و3. لذا يجب على العقدة البادئة التي تستخدم قيمة غير افتراضية لأيٍّ من حدّي التنفيذ ألا ترسل هذا التنسيق الأقدم، بل ترسل بدلًا منه استعلام SQL الأصلي، ليعيد المستقبِل الأقدم بناء الحدود من إعدادات الاستعلام.

التهيئة

يتناول هذا القسم معاملات الضبط التي تحدد سلوك اتصالات البروتوكول الأصلي: تستند القيم الافتراضية الواردة أدناه إلى إصدار حديث من الخادم، وقد تختلف من إصدار إلى آخر ومن بيئة نشر إلى أخرى.

إعدادات طبقة النقل

خيارات المقبس

المهلات

تتداخل المهلات على النحو التالي:
تُفعَّل آلية keepalive الخاصة بنظام التشغيل أولًا، وقد تكتشف الأطراف المتصلة المعطّلة بصمت على مستوى نواة النظام. وتُعدّ مهلة الاستقبال (timeout) على مستوى التطبيق خط الدفاع التالي. أما مهلة الخمول (idle timeout) فهي الملاذ الأخير الذي يُنهي الاتصالات التي ظلت غير مستخدمة لفترة طويلة.

حدود الاتصالات

يمكن للاتصال الذي يرسل الاستعلامات بانتظام أن يبقى مفتوحًا إلى أجل غير مسمى، إذ لا تُغلق سوى الاتصالات الخاملة بعد ساعة، ولا يوجد حد أقصى افتراضي لعمر الاتصال.

إعدادات طبقة التطبيق

تُرسَل هذه الإعدادات مع كل استعلام ضمن قائمة الإعدادات في حزمة Query، وتُغيّر ما يرسله الخادم في تنسيق النقل أو طريقة تأطيره.

الضغط

تتحكم علامة compression في حزمة Query (الحقل 6) في تفعيل الضغط وتعطيله، بينما تحدد هذه الإعدادات المرمِّز المستخدم عند تفعيله.

دفق السجلات

عند ضبط send_logs_level على أي قيمة غير "none"، يُرسل الخادم حزم Log أثناء تنفيذ الاستعلام.

الإبلاغ عن التقدّم

هذه القيمة حدٌّ أدنى مستهدف، وليست حدًّا أقصى صارمًا؛ إذ قد يرسل الخادم حزم Progress بوتيرة أقل حين لا يُنجز الاستعلام العمل بسرعة كافية.

غلاف النتيجة

INSERT غير المتزامن

التتبع الموزع

إعدادات خارج النطاق

يُخلط أحيانًا بين هذه الإعدادات وإعدادات مستوى البروتوكول، غير أنها تتحكم في تنفيذ SQL أو التخزين أو استخدام CPU، لا في سلوك صيغة النقل. ولا يحتاج تطبيق البروتوكول إلى معالجتها معالجةً خاصة.
  • max_threads — درجة التوازي أثناء تنفيذ الاستعلام.
  • max_memory_usage — الحد الأقصى للذاكرة لكل استعلام.
  • max_block_size، preferred_block_size_bytes — تحديد أحجام الكتل داخليًا أثناء معالجة الاستعلام؛ أما كتل صيغة النقل فمستقلة عنها.
  • compile_expressions — التصريف الفوري (JIT)؛ ويقتصر أثره على CPU.
  • async_insert_max_data_size — المخزن المؤقت لقائمة الانتظار على جانب الخادم.
  • جميع إعدادات input_format_* وoutput_format_* باستثناء عائلة input_format_native_* / output_format_native_* — فالإعدادات غير الخاصة بـ native تختار صيغًا أخرى أو تضبطها (عبر HTTP مثلًا)، ولا تغيّر كتل Data في البروتوكول الأصلي.
أما إعدادات *_native_* فهي الاستثناء: إذ تغيّر البايتات داخل كتل Data في البروتوكول الأصلي، ولذا يجب على تطبيق البروتوكول أن يأخذها في الحسبان. فالإعداد output_format_native_encode_types_in_binary_format يحوّل حقل type الخاص بالعمود من سلسلة نصية إلى ترميز ثنائي للنوع (باستثناء الاستعلام الثانوي، راجع تخطيط صيغة النقل للعمود)، والإعداد output_format_native_write_json_as_string يُخرج أعمدة JSON بنوع String، والإعداد output_format_native_use_flattened_dynamic_and_json_serialization يختار تخطيط Dynamic/JSON المسطّح (FLATTENED). ولأن هذه الإعدادات تؤثر في متن الكتلة لا في الغلاف الخارجي للحزمة، فهي موصوفة في مواصفة التنسيق الأصلي — راجع تخطيط صيغة النقل للعمود والأنواع ذات الإصدارات.

مسرد المصطلحات

Cancel — حزمة يرسلها العميل (من النوع 3) لإلغاء استعلام قيد التنفيذ. لا تتناول هذه الصفحة مواصفاتها بالتفصيل. وسم نهاية بيانات العميل — حزمة Data فارغة (0 أعمدة، 0 صفوف) يرسلها العميل لإغلاق دفق الإدخال. ويختلف موضعها باختلاف نوع الاستعلام:
  • الاستعلام العادي (SELECT وغيره): يُرسَل بعد حزمة Query وأي حزم Data خاصة بالجداول الخارجية، للإشارة إلى “انتهاء البيانات الخارجية”. عندئذٍ يبدأ الخادم بالتنفيذ.
  • INSERT: لا يرسل العميل وسمًا قبل المخطط، إذ يرسل الخادم كتلة المخطط أولًا، ثم يبث العميل كتل Data الخاصة بالصفوف، وبعد ذلك فقط يرسل حزمة Data الفارغة لإنهاء دفق الصفوف. أما إرسال وسم فارغ قبل كتلة المخطط فسيُفسَّر على أنه نهاية فورية للصفوف، مما يؤدي إلى فقدان البيانات.
الميزة — تغيير في تنسيق wire أُضيف في إصدار محدد من البروتوكول. وتكون الميزة نشطة متى كان الإصدار المتفق عليه مساويًا لإصدار الميزة أو أعلى منه. راجع الإصدارات وبوابات الميزات. بين الخوادم — تسمية دور تُطلق على حقل لا معنى له إلا في الاستعلامات الموزعة بين الخوادم. ويكتب العملاء الخارجيون فيه قيمة افتراضية (عادةً سلسلة فارغة أو 0 أو false). الإصدار المتفق عليه — min(client_version, server_version)، ويُحسب أثناء المصافحة، ويحدد الميزات النشطة طوال عمر الاتصال. الحزمة — رسالة منقولة تتألف من رمز نوع الحزمة بصيغة VarUInt يليه متن تعتمد صيغته على النوع. راجع غلاف الحزمة. رمز نوع الحزمة — قيمة VarUInt في بداية الحزمة تحدد صيغتها. والقيم المخصصة حاليًا هي من 0 إلى 18. راجع مرجع أنواع الحزم. دفق الاستجابة — تسلسل الحزم التي يرسلها الخادم أثناء تنفيذ الاستعلام. طوله غير محدد، وينتهي بحزمة واحدة فقط: إما EndOfStream (عند النجاح) أو Exception (عند الفشل). راجع مرحلة الاستعلام. كتلة المخطط — كتلة الترويسة (Block تحتوي على أعمدة دون أي صفوف) التي يرسلها الخادم أثناء مرحلة INSERT للإعلان عن بنية الأعمدة المتوقعة قبل أن يرسل العميل البيانات. قائمة الإعدادات — تسلسل من الصفوف (key, flags, value) في متن Query، ينتهي بمفتاح فارغ، ويحمل تهيئة طبقة التطبيق الخاصة بكل استعلام. راجع إعداد. المرحلة — حقل VarUInt في حزمة Query (الحقل 5) يحدد إلى أي مدى ينفّذ الخادم الاستعلام. يرسل العملاء الخارجيون عادةً القيمة 2 (Complete)، بينما تستخدم الاستعلامات الموزعة وخطط تنفيذ الاستعلام المسلسلة القيم الأعلى. راجع الحقل 5 في Query للاطلاع على المجموعة الكاملة من القيم المنقولة. المُنهي — حزمة تُنهي دفقًا. تنتهي استجابة Query بـ EndOfStream (عند النجاح) أو Exception (عند الفشل)، بينما ينتهي دفق إدخال العميل بوسم Data الفارغ.