How to List User-Defined vs. Built-In Functions in SQLite
Understanding SQLite Function Types and Visibility Challenges
SQLite’s extensible architecture allows developers to create custom functions that operate within SQL queries or interact with the database engine programmatically. However, distinguishing between built-in functions (e.g., SUM()
, SUBSTR()
) and user-defined functions (UDFs) registered via APIs like sqlite3_create_function()
is not straightforward. A common misconception arises from assuming that all functions—whether internal, external, or user-created—reside in a unified registry accessible through SQL queries. This misunderstanding leads to confusion when attempting to programmatically enumerate functions, especially in scenarios requiring dynamic introspection of the database environment. The core issue revolves around SQLite’s separation of concerns: built-in functions are part of the SQL execution engine, while UDFs are bound to specific database connections at runtime and lack centralized metadata storage.
The problem is exacerbated by SQLite’s documentation referring to both SQL-invokable functions and C-language API methods as "functions," blurring the line between SQL-operational entities and programmatic interfaces. For example, pragma_function_list
returns a list of SQL-invokable functions but excludes UDFs registered via the C API. Meanwhile, functions exposed through SQLite’s shared library (e.g., sqlite3_open()
) are unrelated to SQL execution and belong to the API layer. Developers must therefore clarify whether they need to introspect SQL-operational functions (used in SELECT
statements), API methods (called from application code), or symbols in compiled binaries (e.g., DLLs). Each category requires distinct approaches for enumeration, and conflating them results in incomplete or inaccurate results.
Why SQLite Does Not Provide a Direct Method to List User-Defined Functions
Architectural Separation Between SQL and API Layers
SQLite’s design strictly separates SQL semantics from its C API. Built-in functions likeLOWER()
orRANDOM()
are hardcoded into the SQL parser and execution engine. User-defined functions, however, are registered at runtime usingsqlite3_create_function()
, which binds them to a specific database connection. These UDFs exist in memory and are not persisted to the database schema or catalog. Consequently, SQLite’s metadata tables (e.g.,sqlite_master
) and PRAGMAs likepragma_function_list
only reflect built-in or SQL-defined functions, not runtime-registered UDFs.Lack of Persistent Metadata for Runtime Objects
Unlike tables or indexes, which are recorded insqlite_master
, UDFs registered via the C API do not create schema entries. This omission is intentional: UDFs are often transient, connection-specific, or context-dependent (e.g., usingsqlite3_create_window_function
for window operations). Persisting them would complicate connection pooling, schema migrations, and multi-threaded environments. Thus, SQLite prioritizes lightweight runtime registration over metadata tracking, leaving developers to manage UDF visibility manually.Ambiguity in Function Terminology
The term "function" in SQLite documentation refers to multiple entities:- SQL Functions: Invoked within SQL statements (e.g.,
ABS(-5)
). - API Functions: C-language methods like
sqlite3_prepare_v2()
. - Shared Library Symbols: Exported symbols in
sqlite3.dll
orlibsqlite3.so
.
Without explicit disambiguation, developers may assume these categories overlap or share a common registry. For instance,pragma_function_list
lists SQL functions but not API methods, while tools likenm
orDumpBin.exe
list shared library symbols unrelated to SQL operations.
- SQL Functions: Invoked within SQL statements (e.g.,
Strategies to Enumerate SQL-Operational and API Functions
Step 1: Use pragma_function_list
with Filtering
The pragma_function_list
command returns a table of all SQL-invokable functions, including built-ins and SQL-defined UDFs (created via CREATE FUNCTION
in SQLite 3.30+). To isolate user-defined functions:
SELECT DISTINCT name
FROM pragma_function_list
WHERE origin NOT IN ('builtin', 'sqlite_define');
Limitations:
- This works only for UDFs defined via SQL’s
CREATE FUNCTION
, which are rare in practice. - UDFs registered via
sqlite3_create_function()
do not appear here, as they lack anorigin
tag.
Step 2: Maintain a Manual UDF Registry
Since SQLite lacks introspection for API-registered UDFs, track them explicitly:
Create a Metadata Table:
CREATE TABLE udf_registry ( name TEXT PRIMARY KEY, arg_count INTEGER, type TEXT CHECK(type IN ('scalar', 'aggregate', 'window')) );
Register UDFs with a Wrapper:
In your application code, wrapsqlite3_create_function()
calls to insert metadata intoudf_registry
:void register_udf( sqlite3 *db, const char *name, int arg_count, void (*func)(sqlite3_context*, int, sqlite3_value**), int func_type ) { sqlite3_create_function(db, name, arg_count, SQLITE_UTF8, NULL, func, NULL, NULL); char *sql = sqlite3_mprintf("INSERT OR IGNORE INTO udf_registry VALUES (%Q, %d, %Q)", name, arg_count, func_type); sqlite3_exec(db, sql, NULL, NULL, NULL); sqlite3_free(sql); }
Advantages:
- Provides a queryable source of truth for UDFs.
- Works across connections if the registry is stored in a shared database.
Step 3: Introspect sqlite_master
for SQL-Defined Functions
SQLite 3.30+ allows creating scalar functions via SQL:
CREATE FUNCTION hex2dec(hex TEXT) RETURNS INTEGER
AS $$ SELECT CAST(printf('%d', CAST('0x' || hex AS INTEGER)) AS INTEGER); $$;
These functions appear in sqlite_master
as type function
:
SELECT name, type
FROM sqlite_master
WHERE type = 'function';
Caveats:
- Limited to SQL-defined functions, not C UDFs.
- Requires SQLite ≥ 3.30.
Step 4: Use Reflection with the C API
For applications with direct C/C++ access, use sqlite3_db_status()
and sqlite3_next_stmt()
to scan active functions:
Iterate Over Prepared Statements:
sqlite3_stmt *stmt = NULL; while ((stmt = sqlite3_next_stmt(db, stmt)) != NULL) { const char *sql = sqlite3_sql(stmt); // Parse SQL to identify function usages (e.g., regex for function calls) }
Hook
sqlite3_create_function()
Calls:
Override the function registration process in debug builds to log UDFs:int (*original_create_function)(sqlite3*, const char*, int, int, void*, void (*)(sqlite3_context*,int,sqlite3_value**), void (*)(sqlite3_context*,int,sqlite3_value**), void (*)(sqlite3_context*)); int traced_create_function( sqlite3 *db, const char *zFuncName, int nArg, int eTextRep, void *pApp, void (*xFunc)(sqlite3_context*,int,sqlite3_value**), void (*xStep)(sqlite3_context*,int,sqlite3_value**), void (*xFinal)(sqlite3_context*) ) { printf("UDF Registered: %s\n", zFuncName); return original_create_function(db, zFuncName, nArg, eTextRep, pApp, xFunc, xStep, xFinal); } // Override in testing environments original_create_function = sqlite3_create_function; sqlite3_create_function = traced_create_function;
Use Cases:
- Debugging or testing environments where UDF registration must be audited.
- Not suitable for production due to performance overhead.
Step 5: Analyze Shared Library Symbols
For compiled SQLite extensions or UDFs distributed as shared libraries, use platform-specific tools to list exported symbols:
- Windows:
dumpbin /exports sqlite3.dll | findstr "sqlite3_"
- Linux/macOS:
nm -gD libsqlite3.so | grep ' T ' | awk '{print $3}'
Insights:
- Lists all C API functions (e.g.,
sqlite3_open_v2
,sqlite3_create_function
). - Does not correlate with SQL-invokable functions.
Step 6: Query SQLite’s Internal Hash Table (Advanced)
SQLite maintains a private hash table (sqlite3GlobalFunctions
) for built-in functions. While undocumented and volatile, it can be accessed in debug builds:
extern const sqlite3APIs;
void list_builtins() {
FuncDefHash *pHash = &sqlite3GlobalFunctions;
for (int i=0; i<pHash->nSlot; i++) {
FuncDef *p = pHash->a[i];
while (p) {
printf("Built-in: %s\n", p->zName);
p = p->pNext;
}
}
}
Risks:
- Relies on internal implementation details that may change without notice.
- Requires recompiling SQLite with debug symbols.
Step 7: Use Extension Loader Metadata (SQLite 3.39+)
SQLite 3.39 introduced sqlite3_load_extension()
enhancements allowing optional metadata tables. If your UDFs are packaged as extensions, design them to register metadata:
int sqlite3_extension_init(
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
) {
sqlite3_create_function(db, "my_udf", 1, SQLITE_UTF8, NULL, my_udf, NULL, NULL);
sqlite3_exec(db,
"CREATE TABLE IF NOT EXISTS extension_meta ("
"func_name TEXT, "
"func_type TEXT);"
"INSERT INTO extension_meta VALUES ('my_udf', 'scalar');",
NULL, NULL, NULL);
return SQLITE_OK;
}
Query:
SELECT func_name FROM extension_meta;
Best Practices:
- Standardize metadata table names across extensions.
- Combine with
sqlite3_auto_extension()
for automatic registration.
Final Recommendations
- For SQL-Only Contexts: Use
pragma_function_list
andsqlite_master
to list SQL-defined functions, but recognize their limitations for API-registered UDFs. - For Native Applications: Implement a manual UDF registry or hook function registration to track UDFs programmatically.
- For Extension Developers: Embed metadata tables within extensions to self-document available functions.
- For Debugging: Use platform-specific symbol dumpers or override
sqlite3_create_function()
in debug builds.
By aligning your approach with SQLite’s architectural constraints and rigorously distinguishing between SQL functions, API methods, and binary symbols, you can achieve accurate introspection tailored to your use case.