chDB에서는 Python 함수를 SQL로 호출할 수 있는 UDF로 등록할 수 있습니다. 이 함수들은 네이티브 인프로세스 방식으로 실행되므로 하위 프로세스를 생성하거나 직렬화 오버헤드가 발생하지 않습니다. 함수는 타입 안전성을 보장하고 Python 어노테이션을 기반으로 한 자동 유형 추론을 지원하며, NULL 및 예외 처리를 구성할 수 있습니다.
Quick Start
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}!"데코레이터가 적용된 함수는 일반 Python 함수처럼 호출할 수 있습니다:
add(2, 3) # 5 (Python call)
query("SELECT add(2, 3)") # 5 (SQL call)create_function
호출 가능한 모든 항목(lambda, 함수, 메서드)을 명시적인 이름으로 등록합니다:
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에서 import할 수 있습니다:
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)" |
매개변수가 있는 타입에 사용 |
| Python 타입 | 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가 Python 타입 어노테이션을 기반으로 타입을 추론합니다:
| Python 타입 | 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을 Python 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 매개변수는 Python 함수에서 예외가 발생할 때의 동작을 제어합니다.
| 값 | 동작 |
|---|---|
"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)") # NULLNULL 및 예외 처리 함께 사용하기
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 및 시간대 지원
UDF는 시간대를 지원하는 날짜 및 시간 타입을 완벽하게 지원합니다.
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:00DateTime64(고정밀도)
DATETIME64의 기본 scale은 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세션에서 UDF 사용하기
UDF는 전역으로 등록되며 동일한 프로세스 내의 모든 세션에서 사용할 수 있습니다:
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