الانتقال إلى المحتوى الرئيسي

UDFs الدوال المعرّفة من قِبل المستخدم

يدعم ClickHouse عدة أنواع من الدوال المعرّفة من قِبل المستخدم (UDFs):
  • تبدأ Executable UDFs برنامجًا خارجيًا أو برنامجًا نصيًا (بايثون، Bash، إلخ)، وتُمرِّر إليه blocks من البيانات عبر STDIN / STDOUT. استخدمها لدمج التعليمات البرمجية أو الأدوات الحالية دون الحاجة إلى إعادة تجميع ClickHouse. وهي تفرض overhead أعلى لكل استدعاء مقارنةً بالخيارات التي تعمل داخل العملية، لذا فهي الأنسب للمنطق الأثقل أو عند الحاجة إلى runtime مختلف.
  • تُعرَّف SQL UDFs باستخدام CREATE FUNCTION بالكامل ضمن SQL. وتُضمَّن/تُوسَّع داخل query plan (من دون حدٍّ فاصل على مستوى العملية)، مما يجعلها lightweight ومثالية لإعادة استخدام منطق expression أو تبسيط columns المحسوبة المعقدة.
  • تُشغِّل Experimental WebAssembly UDFs تعليمات برمجية مُجمَّعة إلى WebAssembly داخل sandbox ضمن server process. وهي توفّر overhead أقل لكل استدعاء مقارنةً بالبرامج التنفيذية الخارجية، مع عزل أفضل من الامتدادات native، مما يجعلها مناسبة للخوارزميات المخصّصة المكتوبة بلغات يمكنها الاستهداف إلى WASM (مثل C/C++/Rust).

الدوال المعرفة من قبل المستخدم القابلة للتنفيذ

هذه الميزة متاحة ضمن المعاينة الخاصة في ClickHouse Cloud. يرجى التواصل مع ClickHouse Support عبر https://clickhouse.cloud/support لطلب الوصول.
يمكن لـ ClickHouse استدعاء أي برنامج خارجي قابل للتنفيذ أو برنامج نصي لمعالجة البيانات. يمكن أن يوجد تكوين الدوال المعرفة من قبل المستخدم القابلة للتنفيذ في ملف XML واحد أو أكثر. ويُحدَّد مسار هذا التكوين في المَعلمة user_defined_executable_functions_config. يتضمن تكوين الدالة الإعدادات التالية:
Parameterالوصفمطلوبالقيمة الافتراضية
nameاسم دالةنعم-
commandاسم البرنامج النصي المطلوب تنفيذه، أو الأمر إذا كانت execute_direct تساوي falseنعم-
argumentوصف الوسيطة مع type وname الاختياري الخاص بها. تُوصَف كل وسيطة في إعداد منفصل. ويكون تحديد الاسم ضروريًا إذا كانت أسماء الوسائط جزءًا من التسلسل لدالة معرّفة من قبل المستخدم بتنسيق مثل Native أو JSONEachRowنعمc + argument_number
formatتنسيق تُمرَّر به الوسائط إلى الأمر. ومن المتوقع أيضًا أن يستخدم خرج الأمر التنسيق نفسهنعم-
return_typeنوع القيمة المُعادةنعم-
return_nameاسم القيمة المُعادة. ويكون تحديد اسم الإرجاع ضروريًا إذا كان اسم الإرجاع جزءًا من التسلسل لدالة معرّفة من قبل المستخدم بتنسيق مثل Native أو JSONEachRowاختياريresult
typeنوع التنفيذ. إذا ضُبطت type على executable، فسيُشغَّل أمر واحد. وإذا ضُبطت على executable_pool، فسيُنشأ مجمّع من الأوامرنعم-
max_command_execution_timeالحد الأقصى لوقت التنفيذ بالثواني لمعالجة كتلة من البيانات. يكون هذا الإعداد صالحًا فقط لأوامر executable_poolاختياري10
command_termination_timeoutالمدة بالثواني التي يجب أن ينهي خلالها الأمر عمله بعد إغلاق الأنبوب الخاص به. بعد ذلك، تُرسَل الإشارة SIGTERM إلى العملية التي تنفذ الأمراختياري10
command_read_timeoutمهلة قراءة البيانات من stdout الخاص بالأمر، بالمللي ثانيةاختياري10000
command_write_timeoutمهلة كتابة البيانات إلى stdin الخاص بالأمر، بالمللي ثانيةاختياري10000
pool_sizeحجم مجمّع الأوامراختياري16
send_chunk_headerيتحكم في ما إذا كان سيتم إرسال عدد الصفوف قبل إرسال دفعة من البيانات إلى العمليةاختياريfalse
execute_directإذا كانت execute_direct = 1، فسيُبحث عن command داخل مجلد user_scripts المحدد بواسطة user_scripts_path. ويمكن تحديد وسائط إضافية للبرنامج النصي باستخدام فاصل من المسافات البيضاء. مثال: script_name arg1 arg2. وإذا كانت execute_direct = 0، فسيُمرَّر command كوسيطة إلى bin/sh -cاختياري1
lifetimeفترة إعادة تحميل الدالة بالثواني. إذا ضُبطت على 0 فلن تُعاد تحميل الدالةاختياري0
deterministicما إذا كانت الدالة حتمية (تعيد النتيجة نفسها للإدخال نفسه)اختياريfalse
stderr_reactionكيفية التعامل مع خرج stderr الخاص بالأمر. القيم: none (تجاهل)، log (تسجيل كل خرج stderr فورًا)، log_first (تسجيل أول 4 KiB بعد الخروج)، log_last (تسجيل آخر 4 KiB بعد الخروج)، throw (طرح استثناء فورًا عند أي خرج من stderr). عند استخدام log_first أو log_last مع رمز خروج غير صفري، يُضمَّن محتوى stderr في رسالة الاستثناءاختياريlog_last
check_exit_codeإذا كانت القيمة true، فسيتحقق ClickHouse من رمز خروج الأمر. ويتسبب رمز الخروج غير الصفري في حدوث استثناءاختياريtrue
يجب أن يقرأ الأمر الوسائط من STDIN وأن يكتب النتيجة إلى STDOUT. ويجب أن يعالج الأمر الوسائط على نحو تكراري. أي إنه بعد معالجة دفعة من الوسائط، يجب أن ينتظر الدفعة التالية.

الدوال المعرّفة من قبل المستخدم القابلة للتنفيذ

أمثلة

UDF من برنامج نصي مضمن

أنشئ test_function_sum يدويًا مع ضبط execute_direct على القيمة 0 باستخدام إعدادات XML أو YAML.
الملف test_function.xml (/etc/clickhouse-server/test_function.xml مع إعدادات المسار الافتراضية).
/etc/clickhouse-server/test_function.xml
<functions>
    <function>
        <type>executable</type>
        <name>test_function_sum</name>
        <return_type>UInt64</return_type>
        <argument>
            <type>UInt64</type>
            <name>lhs</name>
        </argument>
        <argument>
            <type>UInt64</type>
            <name>rhs</name>
        </argument>
        <format>TabSeparated</format>
        <command>cd /; clickhouse-local --input-format TabSeparated --output-format TabSeparated --structure 'x UInt64, y UInt64' --query "SELECT x + y FROM table"</command>
        <execute_direct>0</execute_direct>
        <deterministic>true</deterministic>
    </function>
</functions>

Query
SELECT test_function_sum(2, 2);
Result
┌─test_function_sum(2, 2)─┐
│                       4 │
└─────────────────────────┘

UDF من برنامج نصي بايثون

في هذا المثال، ننشئ UDF يقرأ قيمة من STDIN ويُرجعها كسلسلة نصية. أنشئ test_function باستخدام تهيئة XML أو YAML.
الملف test_function.xml (/etc/clickhouse-server/test_function.xml مع إعدادات المسار الافتراضية).
/etc/clickhouse-server/test_function.xml
<functions>
    <function>
        <type>executable</type>
        <name>test_function_python</name>
        <return_type>String</return_type>
        <argument>
            <type>UInt64</type>
            <name>value</name>
        </argument>
        <format>TabSeparated</format>
        <command>test_function.py</command>
    </function>
</functions>

أنشئ ملف البرنامج النصي test_function.py داخل مجلد user_scripts (/var/lib/clickhouse/user_scripts/test_function.py مع إعدادات المسار الافتراضية).
#!/usr/bin/python3

import sys

if __name__ == '__main__':
    for line in sys.stdin:
        print("Value " + line, end='')
        sys.stdout.flush()
Query
SELECT test_function_python(toUInt64(2));
Result
┌─test_function_python(2)─┐
│ Value 2                 │
└─────────────────────────┘

اقرأ قيمتين من STDIN وأعِد مجموعهما على شكل كائن JSON

أنشئ test_function_sum_json باستخدام وسيطات مُسمّاة وتنسيق JSONEachRow، وذلك باستخدام تهيئة XML أو YAML.
الملف test_function.xml (/etc/clickhouse-server/test_function.xml مع إعدادات المسار الافتراضية).
/etc/clickhouse-server/test_function.xml
<functions>
    <function>
        <type>executable</type>
        <name>test_function_sum_json</name>
        <return_type>UInt64</return_type>
        <return_name>result_name</return_name>
        <argument>
            <type>UInt64</type>
            <name>argument_1</name>
        </argument>
        <argument>
            <type>UInt64</type>
            <name>argument_2</name>
        </argument>
        <format>JSONEachRow</format>
        <command>test_function_sum_json.py</command>
    </function>
</functions>

أنشئ ملف البرنامج النصي test_function_sum_json.py داخل المجلد user_scripts (/var/lib/clickhouse/user_scripts/test_function_sum_json.py مع إعدادات المسار الافتراضية).
#!/usr/bin/python3

import sys
import json

if __name__ == '__main__':
    for line in sys.stdin:
        value = json.loads(line)
        first_arg = int(value['argument_1'])
        second_arg = int(value['argument_2'])
        result = {'result_name': first_arg + second_arg}
        print(json.dumps(result), end='\n')
        sys.stdout.flush()
Query
SELECT test_function_sum_json(2, 2);
Result
┌─test_function_sum_json(2, 2)─┐
│                            4 │
└──────────────────────────────┘

استخدام المعلمات في إعداد command

يمكن للدوال المعرّفة من قبل المستخدم القابلة للتنفيذ أخذ معلمات ثابتة تُضبط في إعداد command (يعمل هذا فقط مع الدوال المعرّفة من قبل المستخدم من النوع executable). ويتطلب ذلك أيضًا الخيار execute_direct لضمان عدم وجود ثغرة ناتجة عن توسيع وسيطات الصدفة.
ملف test_function_parameter_python.xml (/etc/clickhouse-server/test_function_parameter_python.xml مع إعدادات المسار الافتراضية).
/etc/clickhouse-server/test_function_parameter_python.xml
<functions>
    <function>
        <type>executable</type>
        <execute_direct>true</execute_direct>
        <name>test_function_parameter_python</name>
        <return_type>String</return_type>
        <argument>
            <type>UInt64</type>
        </argument>
        <format>TabSeparated</format>
        <command>test_function_parameter_python.py {test_parameter:UInt64}</command>
    </function>
</functions>

أنشئ ملف البرنامج النصي test_function_parameter_python.py داخل المجلد user_scripts (/var/lib/clickhouse/user_scripts/test_function_parameter_python.py مع إعدادات المسار الافتراضية).
#!/usr/bin/python3

import sys

if __name__ == "__main__":
    for line in sys.stdin:
        print("Parameter " + str(sys.argv[1]) + " value " + str(line), end="")
        sys.stdout.flush()
Query
SELECT test_function_parameter_python(1)(2);
Result
┌─test_function_parameter_python(1)(2)─┐
│ Parameter 1 value 2                  │
└──────────────────────────────────────┘

UDF من برنامج نصي shell

في هذا المثال، ننشئ برنامجًا نصيًا لـ shell يُضاعف كل قيمة.
ملف test_function_shell.xml (/etc/clickhouse-server/test_function_shell.xml باستخدام إعدادات المسار الافتراضية).
/etc/clickhouse-server/test_function_shell.xml
<functions>
    <function>
        <type>executable</type>
        <name>test_shell</name>
        <return_type>String</return_type>
        <argument>
            <type>UInt8</type>
            <name>value</name>
        </argument>
        <format>TabSeparated</format>
        <command>test_shell.sh</command>
    </function>
</functions>

أنشئ ملف البرنامج النصي test_shell.sh داخل المجلد user_scripts (/var/lib/clickhouse/user_scripts/test_shell.sh باستخدام إعدادات المسار الافتراضية).
/var/lib/clickhouse/user_scripts/test_shell.sh
#!/bin/bash

while read read_data;
    do printf "$(expr $read_data \* 2)\n";
done
Query
SELECT test_shell(number) FROM numbers(10);
Result
    ┌─test_shell(number)─┐
 1. │ 0                  │
 2. │ 2                  │
 3. │ 4                  │
 4. │ 6                  │
 5. │ 8                  │
 6. │ 10                 │
 7. │ 12                 │
 8. │ 14                 │
 9. │ 16                 │
10. │ 18                 │
    └────────────────────┘

معالجة الأخطاء

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

تقييم تعبيرات الوسائط

في معظم لغات البرمجة، قد لا يُقيَّم أحد الوسائط مع بعض المعاملات. وعادةً ما تكون هذه المعاملات هي && و || و ?:. في ClickHouse، تُقيَّم وسائط الدوال (المعاملات) دائمًا. ويرجع ذلك إلى أن أجزاءً كاملة من الأعمدة تُقيَّم دفعةً واحدة، بدلًا من حساب كل صف على حدة.

تنفيذ الدوال في معالجة الاستعلامات الموزعة

في معالجة الاستعلامات الموزعة، يُنفَّذ أكبر عدد ممكن من مراحل معالجة الاستعلام على الخوادم البعيدة، وتُنفَّذ بقية المراحل (مثل دمج النتائج الوسيطة وكل ما يلي ذلك) على الخادم الطالب. وهذا يعني أن الدوال قد تُنفَّذ على خوادم مختلفة. على سبيل المثال، في الاستعلام SELECT f(sum(g(x))) FROM distributed_table GROUP BY h(y),
  • إذا كان distributed_table يحتوي على shardين على الأقل، فستُنفَّذ الدالتان ‘g’ و ‘h’ على الخوادم البعيدة، بينما تُنفَّذ الدالة ‘f’ على الخادم الطالب.
  • إذا كان distributed_table يحتوي على shard واحد فقط، فستُنفَّذ جميع الدوال ‘f’ و ‘g’ و ‘h’ على خادم هذا الـ shard.
عادةً لا تعتمد نتيجة الدالة على الخادم الذي تُنفَّذ عليه. لكن في بعض الحالات يكون ذلك مهمًا. فعلى سبيل المثال، تستخدم الدوال التي تعمل مع القواميس القاموس الموجود على الخادم الذي تعمل عليه. ومثال آخر هو الدالة hostName، التي تُرجع اسم الخادم الذي تعمل عليه، بحيث يمكن تنفيذ GROUP BY حسب الخوادم في استعلام SELECT. إذا كانت دالة في استعلام ما تُنفَّذ على الخادم الطالب، لكنك تحتاج إلى تنفيذها على الخوادم البعيدة، فيمكنك تغليفها داخل الدالة التجميعية ‘any’ أو إضافتها إلى مفتاح في GROUP BY.

دوال SQL المعرفة من قبل المستخدم

يمكن إنشاء دوال مخصصة من تعبيرات lambda باستخدام عبارة CREATE FUNCTION. ولحذف هذه الدوال، استخدم عبارة DROP FUNCTION.

دوال WebAssembly المعرّفة من قبل المستخدم

تتيح لك دوال WebAssembly المعرّفة من قبل المستخدم (WASM UDFs) تشغيل تعليمات برمجية مخصّصة مُجمَّعة إلى WebAssembly داخل عملية خادم ClickHouse.

البدء السريع

فعِّل دعم WebAssembly التجريبي في تهيئة ClickHouse:
<clickhouse>
    <allow_experimental_webassembly_udf>true</allow_experimental_webassembly_udf>
</clickhouse>
أدرِج وحدة WASM المُجمَّعة في جدول النظام:
INSERT INTO system.webassembly_modules (name, code)
SELECT 'my_module', base64Decode('AGFzbQEAAAA...');
أنشئ FUNCTION باستخدام وحدة WASM الخاصة بك:
CREATE FUNCTION my_function
LANGUAGE WASM
ABI ROW_DIRECT
FROM 'my_module'
ARGUMENTS (x UInt32, y UInt32)
RETURNS UInt32;
استخدم FUNCTION في استعلاماتك:
SELECT my_function(10, 20);

مزيد من المعلومات

راجِع وثائق دوال WebAssembly المعرّفة من قبل المستخدم لمزيد من التفاصيل.
آخر تعديل في ٢٥ يونيو ٢٠٢٦