C调用SQLite的sqlite3_mprintf拼接REPLACE参数时引号丢失如何解决
基于C语言使用SQLite C API操作数据库时,需要移除字符串数据中的逗号完成输入内容的匹配查询,计划使用SQLite内置REPLACE(data, ',', '')函数实现逗号移除逻辑。
程序使用sqlite3_mprintf()等SQLite C API编写查询逻辑,目标是查询匹配输入的行,校验时忽略字段中的逗号。
原问题代码如下:
sqlite3_stmt *main_stmt; const char* comma = "','"; const char* removeComma = "''" char *zSQL; zSQL = sqlite3_mprintf(SELECT * FROM table WHERE (REPLACE(colA||colB, %w, %w) LIKE %%%q%%, comma, removeComma, input); int result = sqlite3_prepare_v2(database, zSQL, -1, &main_stmt, 0);
实际运行时,sqlite3_mprintf的替换格式化类型会处理传入参数中的单引号,导致REPLACE函数入参不符合预期:原本期望生成的SQL语句中REPLACE部分为REPLACE(colA||colB, ',', ''),实际生成的SQL变为REPLACE(colA||colB, ,, ),无法正确移除colA与colB拼接字段中的逗号,查询结果不符合预期。需要可行的传参方案,将带单引号的逗号、带单引号的空字符串正确作为REPLACE函数的前两个参数传入。
问题根因
%w、%q这类sqlite3_mprintf格式化符的设计作用,就是自动为传入的内容添加SQL语法要求的包裹符号、转义内部特殊字符:
%q用于字符串字面量,自动给内容包裹单引号,转义内部单引号%w用于表名、列名这类标识符,自动给内容包裹双引号,转义内部双引号
提前在参数值里手动写外层单引号时,这些单引号会被格式化符判定为字符串内部的普通字符做转义处理,再加上原代码误用了适合标识符的%w格式化符传字符串值,最终生成的SQL自然会出现语法错误。
方案1:传入原始字符串,由格式化符自动生成合法SQL
这是最符合sqlite3_mprintf设计逻辑的写法,不需要提前在参数里包裹单引号,直接传入要替换的原始内容,使用对应类型的格式化符即可:
- 要匹配替换的目标字符是逗号,直接传入存值为
,的字符串 - 替换后的内容是空字符串,直接传入空字符串即可
- 字符串字面量统一用
%q做格式化
修正后的代码:
sqlite3_stmt *main_stmt; // 直接传原始值,不要手动包单引号 const char* comma = ","; const char* removeComma = ""; char *zSQL; // SQL语句本身要包裹在双引号内,修正LIKE子句的引号和通配符写法 zSQL = sqlite3_mprintf( "SELECT * FROM table WHERE REPLACE(colA||colB, %q, %q) LIKE '%%%q%%'", comma, removeComma, input ); int result = sqlite3_prepare_v2(database, zSQL, -1, &main_stmt, 0); // 注意:sqlite3_mprintf申请的内存需要手动释放,避免泄漏 sqlite3_free(zSQL);
该写法生成的SQL完全符合预期:SELECT * FROM table WHERE REPLACE(colA||colB, ',', '') LIKE '%待匹配输入%'
方案2:固定参数直接硬编码到SQL语句中
由于REPLACE的前两个参数是固定值(永远是将逗号替换为空),完全不需要通过格式化参数动态传入,直接写在SQL字符串里即可,仅对用户输入的动态内容做格式化处理,写法更简洁,出错概率更低:
sqlite3_stmt *main_stmt; char *zSQL; zSQL = sqlite3_mprintf( "SELECT * FROM table WHERE REPLACE(colA||colB, ',', '') LIKE '%%%q%%'", input ); int result = sqlite3_prepare_v2(database, zSQL, -1, &main_stmt, 0); sqlite3_free(zSQL);
额外提示:如果传入的内容是用户可控的动态输入,更推荐使用
sqlite3_bind_*系列函数做参数绑定,相比字符串拼接的写法可以完全避免SQL注入风险,也不需要手动处理各类转义逻辑。
内容的提问来源于stack exchange,提问作者Dimony

