Python user-defined functions (UDF)
chDB allows you to register Python functions as SQL-callable UDFs. These run natively in-process — no subprocess spawning, no serialization overhead. Functions are type-safe, support automatic type inference from Python annotations, and offer configurable NULL and exception handling.
Quick start
Examples in this guide run query() with the default CSV output format. Inline comments show the logical result values; the raw output prints NULL as \N and applies CSV quoting to string and date values (for example "Hello, world!").
Registration methods
@func decorator
The simplest way to register a UDF. The function's __name__ becomes the SQL function name.
The decorated function remains callable as normal Python:
create_function
Register any callable (lambda, function, method) with an explicit name:
drop_function
Remove a registered UDF. Dropping a name that is not registered does nothing, so it is safe to call unconditionally:
Registering a name that is already registered raises an error — UDFs are not silently replaced. Call drop_function(name) first to re-register a function, for example when re-running a notebook cell.
Type system
Available types
All types are importable from chdb.sqltypes:
Specifying types
Types can be provided in four ways:
| Method | Example | Description |
|---|---|---|
ChdbType constant | INT64, STRING | Imported from chdb.sqltypes |
| ClickHouse type string | "Int64", "String" | Standard ClickHouse type names |
| Parameterized string | "DateTime('UTC')", "DateTime64(6)" | For types with parameters |
| Python type | int, str, float | Passed directly in arg_types/return_type, or used as type annotations in the function signature |
Automatic type inference
When arg_types or return_type is omitted, chDB infers types from Python type annotations:
| Python Type | ClickHouse Type |
|---|---|
bool | Bool |
int | Int64 |
float | Float64 |
str | String |
bytes | String |
bytearray | String |
datetime.date | Date |
datetime.datetime | DateTime64(6) |
If arg_types is provided explicitly, it must cover all parameters — partial explicit + partial inferred is not supported. This applies to both create_function and the @func decorator: either specify types for all parameters, or omit them entirely and let chDB infer from annotations.
A return type is always required: if return_type is omitted and the function has no return annotation, registration fails. Argument types, by contrast, are optional — a parameter with neither an explicit type nor an annotation accepts any supported input type dynamically.
NULL handling
The on_null parameter controls behavior when any input argument is NULL.
| Value | Behavior |
|---|---|
"skip" (default) | Return NULL immediately without calling the function |
"pass" | Convert NULL to Python None and call the function normally |
You can also use the enum: chdb.NullHandling.SKIP / chdb.NullHandling.PASS.
Example: default (skip)
Example: pass NULL as None
Example: multiple arguments
Exception handling
The on_error parameter controls behavior when the Python function raises an exception.
| Value | Behavior |
|---|---|
"propagate" (default) | Raise the exception as a SQL error |
"ignore" | Catch the exception and return NULL for that row |
You can also use the enum: chdb.ExceptionHandling.PROPAGATE / chdb.ExceptionHandling.IGNORE.
Example: default (propagate)
Example: ignore errors
Combining NULL and exception handling
The on_null and on_error options can be combined:
| on_null | on_error | NULL input | Exception |
|---|---|---|---|
"skip" | "propagate" | Return NULL | Raise error |
"skip" | "ignore" | Return NULL | Return NULL |
"pass" | "propagate" | Call with None | Raise error |
"pass" | "ignore" | Call with None | Return NULL |
DateTime and timezone support
UDFs fully support date and time types with timezone awareness.
Date types
DateTime with timezones
DateTime64 (high precision)
DATETIME64 defaults to scale 6 (microseconds):
- Input
DateTime/DateTime64values carry timezone info from ClickHouse - Output
datetimeobjects preserve timezone info - Timezone conversion is handled automatically
Using UDFs with sessions
UDFs are registered globally and available across all sessions in the same process: