> ## Documentation Index
> Fetch the complete documentation index at: https://clickhouse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

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

> أنشئ دوال UDF أصلية في بايثون ضمن chDB، مع وسائط محددة الأنواع ومعالجة NULL والتحكم في الاستثناءات.

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

<div id="quick-start">
  ## البدء السريع
</div>

```python theme={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
```

<Note>
  تشغّل الأمثلة الواردة في هذا الدليل `query()` باستخدام تنسيق الإخراج CSV الافتراضي. وتُظهر التعليقات المضمّنة قيم النتائج المنطقية، بينما يطبع الإخراج الخام `NULL` بالشكل `\N` ويطبّق اقتباس CSV على قيم السلاسل النصية والتواريخ (مثل `"Hello, world!"`).
</Note>

<div id="registration-methods">
  ## طرق التسجيل
</div>

<div id="func-decorator">
  ### المُزيِّن `@func`
</div>

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

```python theme={null}
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}!"
```

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

```python theme={null}
add(2, 3)       # 5 (Python call)
query("SELECT add(2, 3)")  # 5 (SQL call)
```

<div id="create-function">
  ### `create_function`
</div>

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

```python theme={null}
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
```

<div id="drop-function">
  ### `drop_function`
</div>

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

```python theme={null}
from chdb import drop_function

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

<Note>
  تؤدي محاولة تسجيل اسم مسجّل مسبقًا إلى ظهور خطأ — لا تُستبدل UDFs تلقائيًا. استدعِ `drop_function(name)` أولًا لإعادة تسجيل الدالة، مثلًا عند إعادة تشغيل خلية في دفتر ملاحظات.
</Note>

<div id="type-system">
  ## نظام الأنواع
</div>

<div id="available-types">
  ### الأنواع المتاحة
</div>

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

```python theme={null}
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,
)
```

<div id="specifying-types">
  ### تحديد الأنواع
</div>

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

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

```python theme={null}
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
```

<div id="automatic-type-inference">
  ### الاستدلال التلقائي للأنواع
</div>

عند حذف `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)` |

```python theme={null}
@func()
def process(name: str, age: int) -> str:
    return f"{name} is {age} years old"

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

<Note>
  إذا تم تحديد `arg_types` صراحةً، فيجب أن يشمل **جميع** المعلمات — إذ لا يُدعم الجمع بين التحديد الصريح الجزئي والاستدلال الجزئي. ينطبق ذلك على كلٍّ من `create_function` ومزيّن `@func`: حدّد أنواع جميع المعلمات، أو احذفها بالكامل ودع chDB يستنتجها من التعليقات التوضيحية.
</Note>

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

<div id="null-handling">
  ## التعامل مع NULL
</div>

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

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

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

<div id="null-skip">
  ### مثال: default (تخطي)
</div>

```python theme={null}
@func(return_type="Int64")
def increment(x: int) -> int:
    return x + 1

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

<div id="null-pass">
  ### مثال: تمرير NULL على أنه `None`
</div>

```python theme={null}
@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
```

<div id="null-multiple-args">
  ### مثال: وسائط متعددة
</div>

```python theme={null}
@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
```

<div id="exception-handling">
  ## معالجة الاستثناءات
</div>

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

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

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

<div id="exception-propagate">
  ### مثال: default (تمرير)
</div>

```python theme={null}
@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
```

<div id="exception-ignore">
  ### مثال: تجاهل الأخطاء
</div>

```python theme={null}
@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
```

<div id="combining-null-and-exception">
  ## الجمع بين معالجة NULL والاستثناءات
</div>

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

| on\_null | on\_error     | إدخال NULL                | استثناء    |
| -------- | ------------- | ------------------------- | ---------- |
| `"skip"` | `"propagate"` | إرجاع NULL                | توليد خطأ  |
| `"skip"` | `"ignore"`    | إرجاع NULL                | إرجاع NULL |
| `"pass"` | `"propagate"` | الاستدعاء باستخدام `None` | توليد خطأ  |
| `"pass"` | `"ignore"`    | الاستدعاء باستخدام `None` | إرجاع NULL |

```python theme={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)
```

<div id="datetime-and-timezone">
  ## دعم DateTime والمنطقة الزمنية
</div>

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

<div id="date-types">
  ### أنواع Date
</div>

```python theme={null}
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
```

<div id="datetime-with-timezones">
  ### DateTime مع المناطق الزمنية
</div>

```python theme={null}
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
```

<div id="datetime64">
  ### DateTime64 (دقة عالية)
</div>

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

```python theme={null}
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
```

<Note>
  * تتضمن قيم `DateTime`/`DateTime64` المُدخلة معلومات المنطقة الزمنية من ClickHouse
  * تحتفظ كائنات `datetime` الناتجة بمعلومات المنطقة الزمنية
  * يُعالَج تحويل المنطقة الزمنية تلقائيًا
</Note>

<div id="using-udfs-with-sessions">
  ## استخدام UDFs مع الجلسات
</div>

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

```python theme={null}
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
```
