直接查 sqlite_master 表只能获取建表语句字符串,字段名、类型、约束等需解析 sql 字段内容;应使用 sqlite3_exec 回调逐行处理并轻量解析括号内字段定义,避免 oom 和异常风险。

直接查 sqlite_master 表就能拿到表、索引、视图定义,但字段顺序和类型要自己解析
SQLite 的 sqlite_master 是只读系统表,它不直接暴露列名、数据类型、是否主键等结构细节——这些全藏在 sql 字段的建表语句字符串里。比如一张表的 sql 值可能是:CREATE TABLE users(id INTEGER PRIMARY KEY, name TEXT NOT NULL)。你得自己拆这个字符串,不能靠 PRAGMA table_info(users) 那种现成接口(后者只能查单个表,且不返回 CHECK/FOREIGN KEY 等约束)。
常见错误是直接用 SELECT * FROM sqlite_master WHERE type='table' 后就以为完事了,结果发现 sql 字段全是大段文本,没法直接映射到 C++ 对象字段。
- 必须对
sql字段做轻量级 SQL DDL 解析(不是完整 parser,只需提取括号内字段定义) - 注意:
sqlite_master中的sql是 SQLite 生成的“规范化”建表语句,不含注释、换行统一为空格,适合正则或状态机解析 -
type字段值为'table'、'index'、'view'、'trigger',别漏掉'view'(它的sql是 CREATE VIEW 语句)
用 sqlite3_exec + 回调函数安全读取,别用 sqlite3_get_table
sqlite3_get_table 会把整张 sqlite_master 加载进内存并复制字符串,对大数据库(比如含几百个视图+触发器)容易触发 OOM;而 sqlite3_exec 的回调模式按行处理,内存可控,也方便边解析边构建 C++ 结构体。
示例回调逻辑要点:
- 检查
argv[1](即type列)是否为"table"或"view" - 跳过
sqlite_开头的内部表(如sqlite_sequence),除非你真需要它们 - 用
argv[4](即sql列)传给你的 DDL 解析函数,别直接std::string(argv[4])就完事——要判空,因为某些虚拟表可能sql为 NULL - 回调中避免抛异常,SQLite C API 不捕获 C++ 异常,会导致未定义行为
解析 CREATE TABLE 字段定义时,优先匹配括号内逗号分隔的「字段片段」
不要试图写完整 SQL parser。针对 SQLite 的 DDL 特性,用状态机或简单正则即可:先定位第一个 ( 和最后一个 ),再按逗号分割中间内容,对每个字段片段提取名称、类型、约束。
典型字段片段示例:id INTEGER PRIMARY KEY AUTOINCREMENT、email TEXT UNIQUE NOT NULL DEFAULT 'unknown@local'。
- 字段名总在最前面,且不带引号(除非是双引号包裹的标识符,此时需去引号)
- 类型关键词(如
INTEGER、TEXT)紧随其后,但可能被PRIMARY KEY等覆盖;真正类型应取第一个出现的类型词,忽略后续约束词 -
PRIMARY KEY可能出现在字段后(inline)或单独一行(, PRIMARY KEY(id)),后者需额外解析括号内的字段名 - 遇到
DEFAULT时,值可能带单引号(字符串)、无引号(数字)、甚至函数调用(如DEFAULT CURRENT_TIMESTAMP)——若只需类型信息,可截断到DEFAULT之前
用 std::regex 解析字段名和类型足够快,但要注意 locale 和 UTF-8 边界
在 Linux/macOS 上默认 std::regex 支持 UTF-8,但 Windows MSVC 的旧版本(std::string_view + 手动查找替代正则,更稳。
一个轻量解析字段的示意代码片段(仅提取字段名和基础类型):
std::string_view sql = argv[4];
size_t start = sql.find('(');
size_t end = sql.rfind(')');
if (start == std::string_view::npos || end == std::string_view::npos || start >= end) return;
std::string_view fields = sql.substr(start + 1, end - start - 1);
<p>// 按逗号分割(需跳过括号/引号内的逗号)
for (auto field : split_fields_by_comma(fields)) {
auto name_end = field.find_first_of(" \t\n\r");
if (name_end != std::string_view::npos) {
std::string_view name = trim(field.substr(0, name_end));
std::string_view rest = trim(field.substr(name_end));
std::string_view type = extract_first_type_word(rest);
// ... 构建 FieldInfo{name, type}
}
}</p>
真正难的不是语法解析,而是 SQLite 允许的方言太灵活:类型名可以是 INT、INTEGER、INT2、INT8,都映射到 same affinity;NUMERIC 和 DECIMAL(10,2) 会被当 numeric affinity 处理。如果你要做类型映射(比如转成 C++ int64_t / std::string),得维护一张 affinity 映射表,而不是硬匹配字符串。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











