错误码分层体系
本文档说明 relational_store 组件的错误码分层体系和转换机制。
错误码分层架构
relational_store 组件采用三层错误码架构:
┌─────────────────────────────────────────────────────────────┐
│ SQLite / RD 内核错误码 │
│ - 文件: sqlite3.h (SQLITE_*) │
│ - 最底层错误,数据库引擎返回 │
└────────────────────────┬────────────────────────────────────┘
│ 转换
▼
┌─────────────────────────────────────────────────────────────┐
│ Native 层错误码 │
│ - 文件: rdb_errno.h │
│ - 基于 E_BASE (14800000) │
│ - 可以修改,但需要协调 │
└────────────────────────┬────────────────────────────────────┘
│ 直接传递 (通过IPC)
▼
┌─────────────────────────────────────────────────────────────┐
│ 接口层错误码 (NDK/JS) │
│ - NDK: relational_store_error_code.h (RDB_E_*) │
│ - JS: napi_rdb_error.cpp (错误码映射表) │
│ - 绝对不能破坏向后兼容 │
└─────────────────────────────────────────────────────────────┘
关键要点
- 没有独立的 IPC 错误码层: IPC 只是传输机制,错误码在跨进程通信中保持不变
- 错误码值保持一致: Native 层和 NDK 层使用相同的错误码值(都是基于 E_BASE = 14800000)
- JS 层映射: JS 层通过映射表将 Native 错误码转换为 JS 错误码和错误消息
各层错误码说明
1. SQLite 和 RD 内核错误码
文件: third_party/sqlite/sqlite3.h
常见错误码:
SQLITE_OK(0): 成功SQLITE_BUSY(5): 数据库文件锁定SQLITE_LOCKED(6): 表锁定SQLITE_CORRUPT(11): 数据库损坏SQLITE_CONSTRAINT(19): 约束冲突
2. Native 层错误码
文件: interfaces/inner_api/rdb/include/rdb_errno.h
基础定义:
constexpr int E_BASE = 14800000; // 基础错误码
constexpr int E_OK = 0; // 成功
constexpr int E_ERROR = E_BASE; // 通用错误
constexpr int E_INVALID_ARGS = (E_BASE + 1); // 14800001
SQLite 映射错误码:
constexpr int E_SQLITE_ERROR = (E_BASE + 0x37); // 14800055
constexpr int E_SQLITE_CORRUPT = (E_BASE + 0x38); // 14800052
constexpr int E_SQLITE_BUSY = (E_BASE + 0x3c); // 14800054
constexpr int E_SQLITE_LOCKED = (E_BASE + 0x3d); // 14800055
constexpr int E_SQLITE_IOERR = (E_BASE + 0x40); // 14800058
3. 接口层错误码 (NDK/JS)
文件:
- NDK:
interfaces/ndk/include/relational_store_error_code.h - JS:
frameworks/js/napi/relationalstore/src/napi_rdb_error.cpp
特点:
- NDK: 与 Native 层使用相同的错误码值(基于 E_BASE)
- JS: 通过映射表转换为 JS 错误码和错误消息
- 绝对不能破坏向后兼容
错误码转换流程
转换链路
SQLite/RD 内核错误 → Native 层错误码 → [通过IPC直接传递] → 接口层错误码
转换示例
场景 1: 数据库损坏
SQLite: SQLITE_CORRUPT (11)
↓
Native: E_SQLITE_CORRUPT (14800052)
↓
NDK: RDB_E_SQLITE_CORRUPT (14800052)
↓
JS: 14800052 + "Failed to open the database because it is corrupted"
场景 2: 数据库忙
SQLite: SQLITE_BUSY (5)
↓
Native: E_SQLITE_BUSY (14800054)
↓
NDK: RDB_E_SQLITE_BUSY (14800054)
↓
JS: 14800054 + "SQLite: The database file is locked"
新增错误码的指导原则
评估流程
- 确定错误码所属层级: SQLite/RD 内核、Native 层、接口层
- 检查是否需要新增接口层错误码:
- 新增功能暴露给应用开发者: 需要新增 NDK/JS 错误码(谨慎评估,破坏兼容性)
- 内部优化: 只在 Native 层新增,映射到现有接口层错误码
- 评估对存量接口的影响: 绝对禁止让存量接口返回新的错误码
允许的场景
✅ 内部新增错误码,映射到现有接口错误码:
// Native 层新增
constexpr int E_NEW_FEATURE_ERROR = (E_BASE + 0x100); // 14800100
// 映射到现有接口错误码,保持兼容性
// NDK/JS 层使用 RDB_E_NOT_SUPPORT 或其他现有错误码
禁止的场景
❌ 让存量接口返回新的错误码:
// 破坏向后兼容,绝对禁止
int32_t RdbStore_Insert(...) {
if (new_condition) {
return RDB_ERR_NEW_ERROR; // ❌ 破坏兼容性!
}
}
❌ 修改现有错误码的含义:
// 改变错误码语义,破坏依赖该错误码的应用
// RDB_ERR_BUSY 原来表示数据库忙,改为表示连接池满
新增错误码模板
Native 层新增错误码
// 1. interfaces/inner_api/rdb/include/rdb_errno.h
constexpr int E_MY_NEW_ERROR = (E_BASE + 0x100);
// 2. 实现文件使用
int32_t MyFunction() {
if (error_condition) {
return E_MY_NEW_ERROR;
}
return E_OK;
}
// 3. 如果需要暴露给应用开发者,同步更新 NDK/JS 层
// NDK: interfaces/ndk/include/relational_store_error_code.h
// JS: frameworks/js/napi/relationalstore/src/napi_rdb_error.cpp
测试要点
单元测试
- 验证 SQLite 错误码到 Native 层错误码的转换
- 验证 Native 层错误码到 NDK/JS 层的传递
兼容性测试
- 确保存量接口在相同场景下返回相同的错误码
- 验证新增错误码不会影响存量接口的错误返回
调试技巧
常见问题
- 错误码不一致: 某层返回了未定义的错误码
- 转换错误: SQLite 错误码到 Native 层错误码的映射错误
- 兼容性破坏: 存量接口返回了新错误码
- JS 层映射缺失: Native 层新增错误码后 JS 层没有对应的映射
调试方法
- 在错误码转换的关键节点添加日志
- 记录 SQLite 错误码、Native 层错误码、接口层错误码的传递过程
- 使用 HiLog 记录错误的层级、错误码值、函数名称
总结
错误码分层是 relational_store 组件错误处理的核心机制。
关键原则:
- 三层架构: SQLite/RD 内核 → Native 层 → 接口层(NDK/JS)
- 错误码一致性: Native 层和 NDK 层使用相同的错误码值(基于 E_BASE)
- IPC 传输: 错误码通过 IPC 直接传递,值保持不变
- 向后兼容: 接口层存量接口不能返回新的错误码
- JS 映射: Native 层新增错误码后,JS 层必须同步添加映射表
新增错误码时:
- Native 层可以自由新增错误码(基于 E_BASE + 偏移)
- 如果需要暴露给应用开发者,必须同步更新 NDK/JS 层
- 绝对禁止让存量接口返回新的错误码,以保持向后兼容性