يتيح لك 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)") # 42drop_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'))") # 2024DateTime مع المناطق الزمنية
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:00DateTime64 (دقة عالية)
تكون القيمة الافتراضية لـ 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