Skip to content
ClickHouse Docs
ClickHouse DocsClickHouse Docs

الدوال المعرّفة من المستخدم في بايثون (UDF)

يتيح لك chDB تسجيل دوال بايثون كدوال UDF قابلة للاستدعاء من SQL. وتعمل هذه الدوال أصليًا ضمن العملية نفسها، دون إنشاء عمليات فرعية أو أعباء إضافية ناتجة عن التسلسل. وهي آمنة الأنواع، وتدعم استنتاج الأنواع تلقائيًا من التعليقات التوضيحية في بايثون، وتوفر معالجة قابلة للتهيئة لقيم NULL والاستثناءات.

البدء السريع

from chdb import query, func
from chdb.sqltypes import INT64

@func([INT64, INT64], INT64)
def add(a, b):
    return a + b

result = query("SELECT add(2, 3)")
print(result)  # 5

طرق التسجيل

المُزيِّن @func

أبسط طريقة لتسجيل UDF. يصبح __name__ الخاص بالدالة اسم دالة SQL.

from chdb import func
from chdb.sqltypes import INT64, STRING

# Explicit types
@func([INT64, INT64], INT64)
def add(a, b):
    return a + b

# Types inferred from annotations
@func()
def multiply(a: int, b: int) -> int:
    return a * b

# Explicit return_type, arg_types inferred from annotations
@func(return_type=STRING)
def greet(name: str):
    return f"Hello, {name}!"

تظل الدالة المُزيَّنة قابلة للاستدعاء كالمعتاد في بايثون:

add(2, 3)       # 5 (Python call)
query("SELECT add(2, 3)")  # 5 (SQL call)

create_function

سجّل أي عنصر قابل للاستدعاء (لامبدا أو دالة أو method) باسم محدد صراحةً:

from chdb import create_function, query
from chdb.sqltypes import INT64, STRING

create_function("strlen", len, arg_types=[STRING], return_type=INT64)
query("SELECT strlen('hello')")  # 5

create_function("double", lambda x: x * 2, arg_types=[INT64], return_type=INT64)
query("SELECT double(21)")  # 42

drop_function

أزل دالة UDF مسجّلة. لا يحدث شيء عند إسقاط اسم غير مسجّل، لذا يمكن استدعاؤها بأمان دون قيد:

from chdb import drop_function

drop_function("strlen")
# query("SELECT strlen('hello')")  # Error: function not found

نظام الأنواع

الأنواع المتاحة

يمكن استيراد جميع الأنواع من chdb.sqltypes:

from chdb.sqltypes import (
    # Boolean
    BOOL,
    # Signed integers
    INT8, INT16, INT32, INT64, INT128, INT256,
    # Unsigned integers
    UINT8, UINT16, UINT32, UINT64, UINT128, UINT256,
    # Floating point
    FLOAT32, FLOAT64,
    # String
    STRING,
    # Date and time
    DATE, DATE32, DATETIME, DATETIME64,
)

تحديد الأنواع

يمكن تحديد الأنواع بأربع طرق:

الطريقة المثال الوصف
ثابت ChdbType INT64, STRING يُستورد من chdb.sqltypes
سلسلة نوع ClickHouse "Int64", "String" أسماء أنواع ClickHouse القياسية
سلسلة مَعْلَمة "DateTime('UTC')", "DateTime64(6)" للأنواع التي تتضمن معاملات
نوع بايثون int, str, float يُمرَّر مباشرةً إلى arg_types/return_type، أو يُستخدم كتعليقات توضيحية للأنواع في توقيع الدالة
from chdb import create_function, func
from chdb.sqltypes import INT64

# All equivalent:
create_function("f1", lambda x: x * 2, arg_types=[INT64], return_type=INT64)
create_function("f2", lambda x: x * 2, arg_types=["Int64"], return_type="Int64")
create_function("f3", lambda x: x * 2, arg_types=[int], return_type=int)

@func()
def f4(x: int) -> int:
    return x * 2

الاستدلال التلقائي للأنواع

عند حذف arg_types أو return_type، يستنتج chDB الأنواع من تعليقات توضيحية للأنواع في بايثون:

نوع بايثون نوع ClickHouse
bool Bool
int Int64
float Float64
str String
bytes String
bytearray String
datetime.date Date
datetime.datetime DateTime64(6)
@func()
def process(name: str, age: int) -> str:
    return f"{name} is {age} years old"

# Equivalent to:
# @func([STRING, INT64], STRING)

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

التعامل مع NULL

يتحكم المَعْلَم on_null في السلوك عند كون أي وسيطة إدخال NULL.

القيمة السلوك
"skip" (افتراضي) يُرجع NULL فورًا دون استدعاء الدالة
"pass" يحوّل NULL إلى None في بايثون ويستدعي الدالة بصورة عادية

يمكنك أيضًا استخدام enum: chdb.NullHandling.SKIP / chdb.NullHandling.PASS.

مثال: default (تخطي)

@func(return_type="Int64")
def increment(x: int) -> int:
    return x + 1

query("SELECT increment(NULL)")  # NULL
query("SELECT increment(5)")     # 6

مثال: تمرير NULL على أنه None

@func(return_type="Int64", on_null="pass")
def null_to_zero(x):
    return 0 if x is None else x + 1

query("SELECT null_to_zero(NULL)")  # 0
query("SELECT null_to_zero(5)")     # 6

مثال: وسائط متعددة

@func(arg_types=["Int64", "Int64"], return_type="Int64", on_null="pass")
def add_or_zero(a, b):
    return (a or 0) + (b or 0)

query("SELECT add_or_zero(NULL, 5)")    # 5
query("SELECT add_or_zero(NULL, NULL)") # 0
query("SELECT add_or_zero(3, 7)")       # 10

معالجة الاستثناءات

تتحكم المعلَمة on_error في السلوك عند قيام دالة بايثون بإطلاق استثناء.

القيمة السلوك
"propagate" (الافتراضي) إطلاق الاستثناء كخطأ SQL
"ignore" التقاط الاستثناء وإرجاع NULL لذلك الصف

يمكنك أيضًا استخدام enum: chdb.ExceptionHandling.PROPAGATE / chdb.ExceptionHandling.IGNORE.

مثال: default (تمرير)

@func(arg_types=["Int64", "Int64"], return_type="Int64")
def divide(a, b):
    return a // b

query("SELECT divide(10, 2)")  # 5
query("SELECT divide(1, 0)")   # Error: ZeroDivisionError

مثال: تجاهل الأخطاء

@func(arg_types=["Int64", "Int64"], return_type="Int64", on_error="ignore")
def safe_divide(a, b):
    return a // b

query("SELECT safe_divide(10, 2)")  # 5
query("SELECT safe_divide(1, 0)")   # NULL

الجمع بين معالجة NULL والاستثناءات

يمكن الجمع بين خياري on_null وon_error:

on_null on_error إدخال NULL استثناء
"skip" "propagate" إرجاع NULL توليد خطأ
"skip" "ignore" إرجاع NULL إرجاع NULL
"pass" "propagate" الاستدعاء باستخدام None توليد خطأ
"pass" "ignore" الاستدعاء باستخدام None إرجاع NULL
@func(
    arg_types=["Int64", "Int64"],
    return_type="Int64",
    on_null="pass",
    on_error="ignore",
)
def robust_divide(a, b):
    if a is None or b is None:
        return -1
    return a // b

query("SELECT robust_divide(10, 2)")     # 5
query("SELECT robust_divide(NULL, 2)")   # -1
query("SELECT robust_divide(1, 0)")      # NULL (exception caught)

دعم DateTime والمنطقة الزمنية

تدعم UDFs أنواع التاريخ والوقت مع دعم المنطقة الزمنية بشكل كامل.

أنواع Date

from datetime import date, timedelta

@func()
def next_day(d: date) -> date:
    return d + timedelta(days=1)

@func()
def get_year(d: date) -> int:
    return d.year

query("SELECT next_day(toDate('2024-06-15'))")  # 2024-06-16
query("SELECT get_year(toDate('2024-06-15'))")  # 2024

DateTime مع المناطق الزمنية

from datetime import timedelta

@func(arg_types=["DateTime('UTC')"], return_type="DateTime('UTC')")
def add_one_hour(dt):
    return dt + timedelta(hours=1)

query("SELECT add_one_hour(toDateTime('2024-01-01 12:00:00', 'UTC'))")  # 2024-01-01 13:00:00

DateTime64 (دقة عالية)

تكون القيمة الافتراضية لـ DATETIME64 هي المقياس 6 (ميكروثانية):

from datetime import timedelta

@func(arg_types=["DateTime64(6, 'UTC')"], return_type="DateTime64(6, 'UTC')")
def add_microsecond(dt):
    return dt + timedelta(microseconds=1)

query("SELECT add_microsecond(toDateTime64('2024-01-01 12:00:00.000000', 6, 'UTC'))")  # 2024-01-01 12:00:00.000001

استخدام UDFs مع الجلسات

تُسجَّل UDFs بشكل عام وتكون متاحة في جميع الجلسات ضمن العملية نفسها:

from chdb import session as chs, func
from chdb.sqltypes import INT64

@func([INT64], INT64)
def double(x):
    return x * 2

sess = chs.Session()
sess.query("CREATE TABLE t (x Int64) ENGINE = Memory")
sess.query("INSERT INTO t VALUES (1), (2), (3)")
result = sess.query("SELECT double(x) FROM t ORDER BY x", "CSV")
print(result)
# 2
# 4
# 6
Navigation