Skip to main content
يُعدِّد تنسيق التأطير إرسال أجزاء مختلفة من استجابة الاستعلام ضمن دفق واحد: أجزاء البيانات، والإجماليات والقيم المتطرفة، وحزم التقدّم، وأحداث ملف التعريف (المقاييس)، وسجلات الخادم — أي كل ما يدعمه البروتوكول الأصلي. ويتيح ذلك تبادل بيانات غنيًا عبر بروتوكول HTTP. تنسيقات التأطير مستقلة عن تنسيقات الإخراج: إذ تغلف البايتات التي ينتجها أي تنسيق إخراج، بفصل أجزاء البايتات هذه وترميزها عند الحاجة. إن ربط حمولات جميع حزم data وtotals وextremes ينتج بالضبط ما كان سينتجه تنسيق الإخراج من دون تأطير. وتُمثَّل الحزم المساعدة (التقدّم والسجلات وأحداث ملف التعريف والاستثناءات) بصيغة JSON. يمكن للتأطير أيضًا أن يجعل تنسيق الإخراج أكثر قدرةً على التعبير — وهذا هو الاستثناء المقصود الوحيد للقاعدة أعلاه. تحذف عائلة تنسيقات JSONCompactEachRow الإجماليات والقيم المتطرفة من مخرجاتها العادية، لأن صفوفها لا يمكن تمييزها عن صفوف البيانات العادية. وفي تنسيق مؤطَّر، يميّز نوع الحزمة بينها، لذا تُصدر هذه التنسيقات صفوف الإجماليات والقيم المتطرفة (بصياغة الصفوف المعتادة) في حزم totals وextremes. بالنسبة إلى هذه التنسيقات، ينتج ربط حمولات حزم data وحدها بالضبط ما كان سينتجه تنسيق الإخراج من دون تأطير، بينما تحمل حزم totals وextremes صفوفًا إضافية لا تتضمنها المخرجات غير المؤطَّرة — لذا ينبغي للعميل الذي يعيد إنشاء المخرجات غير المؤطَّرة من هذا الدفق أن يربط حمولات data فقط. يُختار تنسيق التأطير من خلال الإعداد على مستوى الاستعلام framing_output_format. وينطبق حاليًا على بروتوكول HTTP ويُتجاهل في الواجهات الأخرى. تُضمَّن سجلات الخادم كحزم إذا تم تعيين الإعداد send_logs_level. وتُضمَّن أحداث ملف التعريف إذا كان الإعداد send_profile_events مفعّلًا (وهو مفعّل افتراضيًا). وتُرسل حزم التقدّم وأحداث ملف التعريف مرة واحدة على الأكثر كل interactive_delay ميكروثانية. ينتهي الدفق الناجح بحزمة progress نهائية تحمل العدادات النهائية (result_rows وresult_bytes وmemory_usage)، على غرار حزمة التقدّم النهائية في البروتوكول الأصلي. لا تُعرف هذه العدادات إلا بعد انتهاء الاستعلام، لذلك لا تحملها أي حزمة progress سابقة. وتُكتب حزمة progress النهائية بعد حزم log وprofile_events الختامية التي يصدرها تسجيل انتهاء الاستعلام (مثل إدخال السجل “ذروة استخدام الذاكرة”)، لذا فهي بالفعل الحزمة الأخيرة في الدفق. وعند الفشل، تكون حزمة exception هي الحزمة الأخيرة بدلًا منها، ولا تُكتب حزمة progress ذات العدادات النهائية إطلاقًا — إذ إنها مؤشر انتهاء الدفق بنجاح — حتى إذا حدث الفشل بعد انتهاء الاستعلام نفسه ومعرفة العدادات النهائية بالفعل (مثلًا، عند حدوث فشل أثناء كتابة سجل الاستعلام). لأن هذا الجزء الختامي من الدفق يُكتب بعد تسجيل إدخال QueryFinish في system.query_log، فإن أحداث ملف التعريف الخاصة بإرسال الشبكة للاستعلام (NetworkSendBytes وNetworkSendElapsedMicroseconds) لا تشمل إرسال الحزم الختامية وإغلاق الاستجابة — ولا تشمل، عندما تكون الاستجابة مخزنة مؤقتًا (http_response_buffer_size أو wait_end_of_query)، إرسال جسم الاستجابة المخزن مؤقتًا، الذي لا يُنقل إلا بعد انتهاء الاستعلام. ويتوافق هذا مع البروتوكول الأصلي، الذي يرسل أيضًا سجلاته الختامية وأحداث ملف التعريف بعد إدخال سجل الاستعلام. أي شيء لا يفعّله الاستعلام إلا عبر عبارة SETTINGS الخاصة به — سواء كان تنسيق تأطير أو send_logs_level أو send_profile_events — لا يُعرف إلا بعد تحليل الاستعلام، لذا لا تُلتقط السجلات وأحداث ملف التعريف المقابلة إلا بدءًا من تنفيذ الاستعلام. ولا تُلتقط السجلات وأحداث ملف التعريف الخاصة بمرحلة التحليل والتخطيط والاستدلال إلا عندما يأتي الإعداد من الجلسة أو URL. وبوجه خاص، فإن الاستعلام الذي يفشل أثناء التحليل (قبل تنفيذ المسار) — مثل الإشارة إلى جدول غير معروف — ويُفعّل send_logs_level فقط في عبارة SETTINGS الخاصة به، لا يرسل سوى حزمة exception، وليس سجلات مرحلة التحليل. عيّن send_logs_level في الجلسة أو URL لالتقاط تلك السجلات. ينطبق التحذير نفسه بشأن الاكتشاف المتأخر على send_logs_source_regexp: إذ ترشّح قائمة انتظار السجل الإدخالات حسب المصدر لحظة التقاط كل إدخال، لذا لا يصبح التعبير النمطي المضبوط في عبارة SETTINGS الخاصة بالاستعلام نافذًا إلا من وقت تنفيذ الاستعلام فصاعدًا. تُرشّح حزم log الخاصة بمراحل التحليل والتخطيط والمعالجة وفقًا لقيمة الإعداد في الجلسة أو URL — وتبقى غير مرشحة إذا لم يُضبط الإعداد هناك — لذا قد تتضمن مصادر لا تطابق التعبير النمطي على مستوى الاستعلام. وعلى العكس، فإن الإدخالات التي يستبعدها تعبير نمطي أضيق في الجلسة أو URL تُفقد ولا يستعيدها تعبير أوسع على مستوى الاستعلام. اضبط send_logs_source_regexp في الجلسة أو URL لترشيح دورة حياة الاستعلام بأكملها. إذا حدث استثناء أثناء تنفيذ الاستعلام، يُرسل في حزمة exception (وهي آخر حزمة في الدفق)، بغض النظر عن إعداد http_write_exception_in_output_format، لكي يتمكن العميل دائمًا من تحليل الاستجابة كدفق من الحزم. بعد تسجيل الاستثناء، لا يضيف تنسيق الإخراج أي بايتات أخرى إلى الحمولة: فالاستعلام الذي يفشل قبل إنتاج أي مخرجات لا يرسل أي حزمة data على الإطلاق (ولا حتى البنية الفارغة للمستند الخاصة بالتنسيق)، أما الاستعلام الذي يفشل في منتصف الدفق فيترك الحمولة المتسلسلة مقتطعة عند نقطة الفشل، من دون لاحقة التنسيق — إذ يجب ألا تبدو حمولة الاستعلام الفاشل كمستند مكتمل. يوجد استثناء واحد لهذه القاعدة: إذا فشلت كتابة الحزمة نفسها في منتصفها (مثلًا عند انقطاع الاتصال بعد أن تكون بعض بايتات الحزمة قد وصلت إلى العميل)، يفشل التأطير بصورة مغلقة ويُنهي الدفق من دون حزمة exception نهائية. ولا يعيد محاولة إرسال حزمة كُتبت جزئيًا مطلقًا، لأن إعادة إرسالها ستلحق نسخة مكررة بعد البايتات المقتطعة وتفسد الدفق. في هذه الحالة، يلاحظ العميل استجابة مقتطعة واتصال HTTP مُجهضًا بدلًا من حزمة نهائية سليمة البنية. وتنطبق القاعدة نفسها على الفشل أثناء إغلاق دفق الاستجابة ذاته (تفريغ النتائج المخزنة مؤقتًا، وإنهاء ضغط HTTP، وإغلاق المقبس): إذ يكون جزء من دفق النجاح أو كله قد أصبح بالفعل في تنسيق النقل، لذلك لا يُلحق به شيء — لا حزمة exception ولا كتلة خطأ HTTP العامة — ويلاحظ العميل استجابة مقتطعة واتصالًا مُجهضًا. وتنطبق أيضًا عند فشل تسليم الاستثناء نفسه: فإذا فشلت كتابة حزمة exception النهائية (مثلًا أثناء تصريف السجلات الختامية) بعد إنتاج أي جزء من دفق الحزم — سواء أُرسل بالفعل أو ما زال في مخازن الاستجابة المؤقتة من جانب الخادم (http_response_buffer_size) — يُنهى الدفق كذلك من دون إلحاق أي شيء، لذلك لا يُمزج نص خطأ HTTP عادي مطلقًا ضمن دفق حزم جزئي. ويُعد الفشل أثناء كتابة حقول السلاسل النصية للحزم المساعدة log وprofile_events وexception حزمة مكتوبة جزئيًا أيضًا، بما في ذلك الفشل في كتابة البايتات الأخيرة من مثل هذا الحقل: ينتهي الدفق حينها بهذه الحزمة المقتطعة ولا يحمل أي مُنهٍ على الإطلاق — لا حزمة exception ولا حزمة progress ذات العدادات النهائية — وبذلك يكتشف العميل الذي يتطلب مُنهِيًا الفشل حتى عندما ينجح الاستعلام نفسه. يُطبّق تنسيق تأطير أيضًا على الاستعلامات التي لا تنتج دفق نتائج — مثل INSERT ناجح، أو استعلام DDL، أو أي استعلام آخر بلا مخرجات. لا تحمل هذه الاستجابة حزم data، لكنها تبدّل Content-Type الخاص بالاستجابة إلى تنسيق التأطير وتبث حزم progress وlog وprofile_events، بما يتوافق مع البروتوكول الأصلي. ينتهي الدفق بحزمة progress نهائية تحمل العدادات النهائية (مثلًا result_rows وresult_bytes مع عدد الصفوف المكتوبة لعملية INSERT). وبما أنه لا تُنسّق أي حمولة، فإن تنسيق الإخراج غير ذي صلة بهذه الاستعلامات ولا يؤثر في الدفق المؤطّر.

تنسيقات التأطير المتاحة

None

الخيار الافتراضي. يمرّر بشفافية كل ما ينطبق (البيانات، الإجماليات، القيم القصوى، التقدم) إلى تنسيق الإخراج، ويتجاهل كل ما لا ينطبق (المقاييس، السجلات). وبذلك يعمل كل شيء كما هو افتراضيًا، بما في ذلك التنسيقات التي تمثل التقدم بنفسها، مثل JSONEachRowWithProgress.

EventStream

يؤطّر الحزم على هيئة أحداث يرسلها خادم HTTP، ويضبط Content-Type للاستجابة على text/event-stream; charset=UTF-8; payload=base64. تُرسل كل حزمة كحدث يحمل اسم نوع الحزمة: data، totals، extremes، progress، log، profile_events، exception. وتُرسل حزم التقدم وغيرها من الحزم المساعدة بصيغة JSON. الأحداث التي يرسلها الخادم بروتوكول نصي يتعامل مع فواصل الأسطر، بما في ذلك محارف إرجاع العربة (\r)، بوصفها فواصل للحقول؛ لذا لا تُضمَّن البايتات التي ينتجها تنسيق الإخراج حرفيًا. بل تُرمَّز كتلة من البيانات المنسّقة بترميز base64 في حقل data: واحد، ويُفك ترميزها إلى الحمولة المنسّقة بالكامل، بما فيها جميع أسطرها الجديدة. وهذا ما يوضحه المَعْلَم payload=base64 في Content-Type. إن تسلسل الحمولات المفككة الترميز لحزم data وtotals وextremes يطابق تمامًا، بايتًا ببايت، ما كان سينتجه تنسيق الإخراج دون تأطير، لأي تنسيق إخراج، سواء كان نصيًا أو ثنائيًا (Native، RowBinary) أو تمريرًا خامًا (RawBLOB، TSVRaw). لا تُرمَّز حزم JSON المساعدة (progress، log، profile_events، exception) مطلقًا؛ إذ تُكتب في حقل data: واحد يحتوي على JSON بلا فواصل أسطر. تكتب تنسيقات الإخراج *WithProgress (JSONEachRowWithProgress، JSONCompactEachRowWithProgress) التقدم كصفوف مضمّنة تشكل جزءًا من مخرجاتها. أما تنسيق التأطير فيرسل التقدم كحزم progress منفصلة، لذا فهو غير متوافق مع تنسيقات الإخراج هذه ويرفضها. استخدم تنسيق الإخراج الأساسي، مثل JSONEachRow، مع التأطير، أو التأطير None مع تنسيق *WithProgress.
يتكامل EventStream مع بروتوكول HTTP ويُطلق استثناءً عندما لا يكون منطبقًا.

JSONEachPacketBase64 وJSONEachPacketString

كل حزمة هي كائن JSON في سطر منفصل (JSON محدد بأسطر جديدة، application/x-ndjson) وتحتوي على معلومات عن الحزمة. توضع البايتات التي ينتجها تنسيق الإخراج في الحقل data: بترميز base64 في JSONEachPacketBase64 (وهو مناسب لتنسيقات الإخراج الثنائية)، أو كسلسلة JSON في JSONEachPacketString. يُرمِّز البديلان الحقل data بطرائق مختلفة، لذا يميّز Content-Type الخاص بالاستجابة بينهما، كما في EventStream: يضبط JSONEachPacketBase64 القيمة application/x-ndjson; charset=UTF-8; payload=base64، بينما يضبط JSONEachPacketString القيمة application/x-ndjson; payload=string. لذلك، يمكن للعميل أن يعرف من البيانات الوصفية للاستجابة وحدها ما إذا كان الحقل data يحتاج إلى فك ترميز base64. ولا يضمن charset=UTF-8 سوى JSONEachPacketBase64، لأن ترميز base64 وحده يجعل الدفق بأكمله UTF-8 صالحًا، بغض النظر عن بايتات الحمولة — انظر أدناه. بما أن JSONEachPacketString يضع بايتات الحمولة في سلسلة JSON، فهو مخصص لتنسيقات الإخراج التي تنتج نص UTF-8 صالحًا. يمكن للأعمدة String وFixedString أن تحتوي على بايتات عشوائية، لذا قد تُنتج تنسيقات الإخراج النصية، مثل JSONEachRow أو TSV أو CSV، UTF-8 غير صالح لهذه القيم — تمامًا كما يفعل JSONEachRow الخاص بـ ClickHouse عند استخدام الإعداد الافتراضي output_format_json_validate_utf8 = 0 — وعندئذٍ لا يُضمن أن تكون سلسلة JSON الناتجة، وبالتالي دفق NDJSON بأكمله، صالحة بتشفير UTF-8. لا يتحقق JSONEachPacketString من الحمولة ولا يعيد ترميزها؛ استخدم JSONEachPacketBase64 لنقل البايتات العشوائية بدقة تامة. يرفض JSONEachPacketString مسبقًا، مع ظهور خطأ قبل تنفيذ الاستعلام، تنسيقات الإخراج المعروفة بإنتاج بايتات غير UTF-8: التنسيقات الثنائية (Native، RowBinary)، وتنسيقات التمرير الخام (RawBLOB، TSVRaw)، والتنسيقات التي تكتب في مخرجاتها اسم عمود أو اسم نوع بيانات أو اسم عنصر Tuple غير UTF-8 من رأس الاستعلام، والتهيئات التي تُكتب فيها القيم الحرفية المستمدة من الإعدادات كما هي بواسطة عمليات التسلسل ولا تكون صالحة بتشفير UTF-8 — الإعدادات format_csv_delimiter وformat_tsv_null_representation / format_csv_null_representation وbool_true_representation / bool_false_representation.
باستخدام JSONEachPacketBase64، تكون حزمة data نفسها كما يلي:

أنواع الحزم

على خلاف حمولات data وtotals وextremes (راجع ملاحظات المطابقة التامة للبايتات أعلاه)، لا تتوفر لحقول السلاسل النصية في الحزم المساعدة (query_id وtext وsource في log، وname في profile_events، ورسالة exception) آلية إفلات باستخدام base64، ويمكن لبعضها، مثل query_id المأخوذ من الاستعلام، أن يحتوي على بايتات عشوائية. تُنقّى هذه الحقول دائمًا لتصبح UTF-8 صالحة، مع استبدال التسلسلات غير الصالحة بمحرف الاستبدال (U+FFFD)، وبذلك تكون الحزم المساعدة دائمًا JSON صالحًا. لم تُنفَّذ بعد معالجة عدة استعلامات في وقت واحد، لكن التصميم يتيح ذلك: يمكن توسيع كل حزمة بمعلومات عن فهرس الاستعلام عند معالجة عدة استعلامات.
آخر تعديل في ٢٧ أغسطس ٢٠٢٦