--- warning: DO NOT CHANGE THIS MANUALLY, THIS IS GENERATED BY https://github.com/duckdb/community-extensions repository, check README there title: func_apply excerpt: | DuckDB Community Extensions Dynamic function invocation - call any scalar function or macro by name at runtime extension: name: func_apply description: Dynamic function invocation - call any scalar function or macro by name at runtime version: 0.4.0 language: C++ build: cmake license: MIT maintainers: - teaguesterling repo: github: teaguesterling/duckdb_func_apply ref: 9f119877ef5c10ef84c8cc593ee98ee199be5302 # variegata pins the v1.5.6 track. scripts/build.py sends a v1.5.6 build here when # present and to `ref` otherwise, and unlike ref_next it has no skip branch -- so # once `ref` is 2.0-only a v1.5.6 build would fail rather than be skipped. Equal to # `ref` while both lines build; advance only to a commit v1.5.6 can still build. variegata: 9f119877ef5c10ef84c8cc593ee98ee199be5302 # ref_next == ref (deliberate): validate the shipped release on DuckDB # v2.0-cyanoptera instead of skipping it. A STALE ref_next is worse than none -- # it validates the PREVIOUS release and still reports green. # v0.4.0 (tag v0.4.0) adds func_dispatch() and fixes a return-type bug in # apply(). Verified on this exact commit by a real cyanoptera leg -- build AND # test on v2.0, linux_amd64 and osx_arm64 -- plus all nine stable arches, the # format/tidy checks and the DuckDB version-drift guard. # The v2.0 leg earned this: an earlier commit of the same work compiled on both # lines but DIVERGED at run time (a typed-NULL probe bound on v1.5.6 and raised # a binder error on cyanoptera). Caught by that leg, fixed by removing the # version-dependent path, and re-verified here. ref_next: 9f119877ef5c10ef84c8cc593ee98ee199be5302 # andium (DuckDB v1.4.5 track) intentionally left at the pre-v0.2.0 commit: # v0.3.0, like v0.2.0 before it, is a DuckDB v1.5.4 tree and is not # build-verified against v1.4.5. The v0.2.0 security fix # (GHSA-55g5-vp25-phpg) is delivered on the v1.5.4 track via `ref`; # v1.4.5 users should move to the v1.5.4 track. andium: 2013ac345d6f19e61ee78cacae161eb272cf1837 docs: hello_world: | -- Load the extension LOAD func_apply; -- Call scalar functions dynamically SELECT apply('upper', 'hello world'); -- Result: HELLO WORLD SELECT apply('substr', 'hello world', 7, 5); -- Result: world -- Call table functions dynamically SELECT * FROM apply_table('range', 5); -- Returns: 0, 1, 2, 3, 4 SELECT * FROM apply_table('generate_series', 1, 10, 2); -- Returns: 1, 3, 5, 7, 9 -- Check if a function exists before calling SELECT function_exists('my_custom_func'); -- Result: true/false extended_description: | The FuncApply extension enables dynamic function invocation in DuckDB, allowing you to call any scalar function or macro by name at runtime. This is useful for data-driven transformations, dynamic SQL generation, and building flexible data pipelines. ## Functions | Function | Description | |----------|-------------| | `apply(func_name, ...args)` | Call a function by name with arguments | | `apply_with(func_name, args, kwargs)` | Call a function with arguments as a list | | `apply_table(func_name, ...args)` | Call a table function by name with arguments | | `apply_table_with(func_name, args, kwargs)` | Call a table function with arguments as a list | | `func_dispatch(routes, key, ...args)` | Dispatch to the function named by `routes[key]`, with real return types | | `function_exists(name)` | Check if a function exists | ## Use Cases ### Data-Driven Transformations ```sql -- Store transformation rules in a table CREATE TABLE transforms (column_name VARCHAR, func_name VARCHAR); INSERT INTO transforms VALUES ('name', 'upper'), ('email', 'lower'); -- Apply transformations dynamically SELECT apply(t.func_name, d.value) as result FROM data d JOIN transforms t ON d.column = t.column_name; ``` ### Routing With func_dispatch ```sql CREATE MACRO clean_git(p) AS regexp_replace(p, '^git://', ''); CREATE MACRO clean_http(p) AS regexp_replace(p, '^https?://', ''); CREATE MACRO clean_other(p) AS p; SELECT func_dispatch(MAP{'git': 'clean_git', 'https': 'clean_http', '*': 'clean_other'}, split_part(url, ':', 1), url) AS cleaned FROM urls; ``` The `'*'` entry catches any key the map does not name. Unlike `apply()` with a function name that is not a constant, `func_dispatch` binds every candidate in a constant routing map at plan time, so the result keeps its real type instead of collapsing to `VARCHAR`: ```sql CREATE MACRO pair(x) AS [x, x]; SELECT typeof(func_dispatch(MAP{'a': 'pair'}, 'a', 7)); -- INTEGER[] ``` All routes in a constant map must accept the forwarded arguments, and the session security policy applies to every route including `'*'`. ### Dynamic Function Selection ```sql -- Choose function based on data type SELECT apply( CASE typeof(value) WHEN 'VARCHAR' THEN 'upper' WHEN 'INTEGER' THEN 'abs' END, value ) FROM my_table; ``` ## Supported Function Types - Scalar functions (e.g., `upper`, `abs`, `substr`) - Macros (e.g., `list_sum`, `list_reverse`) Aggregate and table functions are not supported. ## Security Model `apply` and `apply_table` run on the **caller's own context** — they add no privilege beyond the SQL the caller could already execute directly. They are a dynamic-dispatch convenience, **not a sandbox**. For scenarios that embed func_apply and want to restrict which functions can be invoked (e.g. accepting a function name from less-trusted input), an optional, opt-in policy is available. It is **per session** and defaults to `none` (no restriction): | Function | Description | |----------|-------------| | `func_apply_set_security_mode(mode)` | `'none'`, `'blacklist'`, `'whitelist'`, or `'validator'` | | `func_apply_set_whitelist([...])` / `func_apply_set_blacklist([...])` | Set the allowed/denied function names | | `func_apply_lock_security()` | Irreversibly lock the current session's policy | The policy lives on the session's `ClientContext` and does not leak across connections. Versions before 0.2.0 stored it process-globally and could quote-escape injected identifiers — see security advisory GHSA-55g5-vp25-phpg; upgrade to 0.2.0 (DuckDB v1.5.4 track). extension_star_count: 5 extension_star_count_pretty: 5 extension_download_count: 893 extension_download_count_pretty: 893 image: '/images/community_extensions/social_preview/preview_community_extension_func_apply.png' layout: community_extension_doc --- ### Compatibility Matrix This table covers all DuckDB releases that have not reached [end-of-life](https://duckdb.org/release_calendar), including LTS releases, and shows the 2 most recent patch releases of each. Older patch releases are not listed, but the extension may be available for them as well.
| Platform | 1.4.4 | 1.4.5 | 1.5.5 | 1.5.6 | 2.0.0 | | :--- | :---: | :---: | :---: | :---: | :---: | | Linux AMD64 | ✅ | ❌ | ✅ | ✅ | ✅ | | Linux ARM64 | ✅ | ❌ | ✅ | ✅ | ✅ | | macOS AMD64 | ✅ | ❌ | ✅ | ✅ | ✅ | | macOS ARM64 | ✅ | ❌ | ✅ | ✅ | ✅ | | Windows AMD64 | ✅ | ❌ | ✅ | ✅ | ✅ | | Windows ARM64 | ❌ | ❌ | ❌ | ❌ | ❌ | | WebAssembly | ✅ | ❌ | ✅ | ✅ | ✅ | ### Installing and Loading ```sql INSTALL {{ page.extension.name }} FROM community; LOAD {{ page.extension.name }}; ``` {% if page.docs.hello_world %} ### Example ```sql {{ page.docs.hello_world }}``` {% endif %} {% if page.docs.extended_description %} ### About {{ page.extension.name }} {{ page.docs.extended_description }} {% endif %} ### Added Functions
| function_name | function_type | description | comment | examples | |--------------------------------|---------------|---------------------------------------------------------------------------------------------------|---------|-----------------------------------------------------------------| | apply | scalar | NULL | NULL | | | apply_table | table | Dynamically invoke a table function by name with variable arguments. | NULL | [SELECT * FROM apply_table('range', 5)] | | apply_table_with | table | Dynamically invoke a table function with structured args and kwargs. | NULL | [SELECT * FROM apply_table_with('range', args := [5])] | | apply_with | scalar | Dynamically invoke a scalar function with a list of positional args and struct of kwargs. | NULL | [apply_with('upper', args := ['hello'])] | | func_apply_get_security_config | scalar | Get the current security configuration as a JSON string. | NULL | [func_apply_get_security_config()] | | func_apply_lock_security | scalar | Lock the current security configuration to prevent further modifications. | NULL | [func_apply_lock_security()] | | func_apply_set_blacklist | scalar | Set the list of disallowed functions in blacklist security mode. | NULL | [func_apply_set_blacklist(['system', 'read_csv'])] | | func_apply_set_block_default | scalar | Set the default return value when a blocked function is encountered. | NULL | [func_apply_set_block_default('BLOCKED')] | | func_apply_set_on_block | scalar | Set behavior when a blocked function is called ('error', 'null', or 'default'). | NULL | [func_apply_set_on_block('error')] | | func_apply_set_security_mode | scalar | Set the security mode for dynamic function execution ('permissive', 'whitelist', or 'blacklist'). | NULL | [func_apply_set_security_mode('permissive')] | | func_apply_set_validator | scalar | Set a custom SQL validator function to check candidate function invocations. | NULL | [func_apply_set_validator('my_validator')] | | func_apply_set_whitelist | scalar | Set the list of allowed functions in whitelist security mode. | NULL | [func_apply_set_whitelist(['upper', 'lower', 'abs'])] | | func_dispatch | scalar | Dispatch to the function named by routes[key], forwarding the remaining arguments. | NULL | [func_dispatch(MAP {'a': 'upper', '*': 'lower'}, 'a', 'hello')] | | function_exists | scalar | Check if a function exists in the catalog. | NULL | [function_exists('upper')] | ### Overloaded Functions
This extension does not add any function overloads. ### Added Types
This extension does not add any types. ### Added Settings
| name | description | input_type | scope | aliases | typed_value | |----------------|---------------------------------------------------------|------------|--------|---------|-------------| | temp_directory | Set the directory to which temporary files are written. | VARCHAR | GLOBAL | [] | post.db.tmp |