SQLite内核只内置了基础SQL能力,像JSON处理、全文检索、数学函数等需要通过扩展库动态加载。命令行工具sqlite3提供.load命令,可以从共享库文件中加载扩展,把新函数、新虚拟表接入当前连接。这个命令在排查no such function错误、调试自研扩展时非常高频。

.load命令的基本用法与加载流程
进入sqlite3以后,.load后面跟共享库的路径,扩展就会被加载。例如在Linux下加载JSON1扩展:
.load /usr/lib/sqlite3/path/libsqlite3_json1.so
sqlite3会在加载成功后扫描共享库中的入口函数,并根据入口函数注册扩展模块。对于JSON1,入口函数通常是sqlite3_json_init,加载后就可以直接使用json_extract、json_array等函数。
Windows下路径写法不同,比如加载自制DLL:
.load C:\sqlite_ext\my_ext.dll
注意Windows路径中的反斜杠必须保留,不能写成斜杠。如果DLL文件名或路径包含空格,可以用双引号包裹路径,但在命令行里输入时双引号要原样使用,避免被shell拆分。
加载流程可以拆成三步:第一,命令行工具通过操作系统的动态加载接口打开共享库;第二,在库里查找指定的入口函数;第三,调用入口函数,由它向SQLite注册新的函数或模块。整个过程中,.load命令只是前端封装,真正起作用的是C接口sqlite3_load_extension。
扩展库的入口点与编译细节
共享库的入口函数签名是固定的:
int sqlite3_extension_init( sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi );
如果入口函数名不是默认的sqlite3_extension_init,可以在.load命令中追加第二个参数指定。比如某个库的入口叫sqlite3_myext_init,就写:
.load ./myext.so sqlite3_myext_init
编译扩展时要注意共享库必须导出入口函数。Linux常用gcc -shared -fPIC,macOS用dynamiclib,Windows用dll。以Linux为例:
gcc -shared -fPIC -o myext.so myext.c -lsqlite3
如果编译时没有链接sqlite3,仍然可以通过pApi指针调用SQLite提供的API。pApi是SQLite注入给扩展的函数表,里面包含创建函数、设置返回结果等操作。扩展内部不要直接调用sqlite3_xxx函数,尽量使用pApi->xxx,这样能避免符号冲突,也便于跨版本兼容。
入口函数里常见的注册模式:先调用sqlite3_create_function_v2创建标量函数,再调用sqlite3_create_module注册虚拟表模块。每个函数注册完成后可以在SQL中直接使用,无需重启连接。
用C语言编写一个最小扩展
下面实现一个hello函数,接收一个文本参数并返回拼接后的字符串。把这个文件编译成共享库后,用.load加载,然后执行SELECT hello('world')就能得到结果。
#include <sqlite3ext.h>
SQLITE_EXTENSION_INIT1
static void hello_func(
sqlite3_context *ctx,
int argc,
sqlite3_value **argv
) {
const char *name = (const char *)sqlite3_value_text(argv[0]);
char result[256];
snprintf(result, sizeof(result), "hello, %s", name);
sqlite3_result_text(ctx, result, -1, SQLITE_TRANSIENT);
}
int sqlite3_extension_init(
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
) {
SQLITE_EXTENSION_INIT2(pApi);
sqlite3_create_function(db, "hello", 1, SQLITE_UTF8, 0, hello_func, 0, 0);
return 0;
}
这个代码中,SQLITE_EXTENSION_INIT1和SQLITE_EXTENSION_INIT2两个宏用于保存和恢复pApi指针。hello_func是实际函数实现,sqlite3_result_text把结果写回SQLite。注册hello函数后,命令行里执行:
.load ./hello.so
SELECT hello('world');
输出为hello, world。这个最小扩展演示了扩展开发的骨架:定义函数实现、写入口函数、注册函数。实际生产扩展可能还要处理NULL参数、重载、内存管理,但流程一致。
常见问题与安全限制
加载扩展最常见的问题是no such function,先检查扩展路径是否正确,再确认共享库的架构和sqlite3进程是否匹配。64位进程不能加载32位库。Linux下可以用ldd查看依赖,Windows下可以用dumpbin或Dependencies工具检查导出表。
另一个常见问题是not authorized。从SQL层面调用load_extension函数默认关闭,需要先启用扩展加载。命令行工具通常默认允许.load,但很多语言绑定的SQL接口默认禁用,需要额外配置。
安全方面,扩展库是本地代码,拥有当前进程的全部权限。只加载可信来源的共享库,避免加载不明DLL或so文件。生产环境建议使用固定路径、校验哈希,并限制可加载扩展的文件目录。SQLite也支持通过sqlite3_load_extension的zProc参数指定入口,避免导出过多符号。
另外需要注意,.load命令加载的扩展只对当前打开的连接生效,关闭连接后需要重新加载。可以在.sqliterc配置文件中添加.load语句,让每次打开命令行自动加载常用扩展,但也要评估启动时间和错误处理。