如何使用fmt库编译期格式字符串构建防注入的类型安全SQL查询构造器
基于fmtlib的安全SQL查询构造器实现
核心思路
通过参数类型重载实现不同类型的SQL自动转义,直接复用fmt库原生的编译期格式校验能力,无需依赖废弃接口,同时完全保留原生fmt的易用性和类型安全性,从根源避免SQL注入风险。
代码实现
1. 类型化SQL转义函数
针对不同SQL参数类型实现对应的转义逻辑,可按需扩展:
#include <fmt/core.h> #include <string> #include <string_view> #include <type_traits> // 数值类型无需转义直接返回 template<std::integral T> T sql_escape(T value) { return value; } template<std::floating_point T> T sql_escape(T value) { return value; } // 布尔值转SQL合法字面量 int sql_escape(bool value) { return value ? 1 : 0; } // 字符串类型转义单引号,可按需扩展转义其他特殊字符 std::string sql_escape(std::string_view str) { std::string res; res.reserve(str.size() + 4); // 可选:自动包裹单引号,后续格式字符串无需手动加'' // res += '\''; for (char c : str) { if (c == '\'') { res += "''"; } else { res += c; } } // res += '\''; return res; }
2. 核心查询构造函数
完全保留fmt的编译期格式校验能力:
template <typename... Args> std::string query(fmt::format_string<decltype(sql_escape(std::declval<Args>()))...> format_str, Args&&... args) { return fmt::format(format_str, sql_escape(std::forward<Args>(args))...); }
3. 使用示例
// 调用语法和原生fmt完全一致 std::string sql = query("SELECT * FROM user WHERE id = {} AND username = '{}'", 123, "O'Neil"); // 生成的SQL为:SELECT * FROM user WHERE id = 123 AND username = 'O''Neil'
注意事项
- 若启用字符串转义自动包裹单引号,格式字符串中字符串占位符无需手动加
'{}',直接写{}即可,进一步减少手写错误 - 可按需新增
sql_escape重载支持NULL、二进制、日期时间等更多SQL类型,无需修改核心query函数逻辑 - fmt版本低于8.0时,仅需将
fmt::format_string替换为对应版本的编译期格式校验接口即可,整体逻辑无需调整
内容的提问来源于stack exchange,提问作者Justasic
相关产品推荐
相关产品推荐

