- _coerce_value 改为返回 json::value,保留 int/double/bool/null 原始类型 - 新增 _bind_value,按类型调用 sqlite3_bind_int64/double/text/null - WhereClause::params 改为 vector<json::value>,删除 is_null 向量 - INSERT/UPDATE/SELECT/DELETE 统一使用按类型绑定 修复 P1-1(所有值绑定为 TEXT)和 P3-2(_coerce_value null 处理误导)
db2 — C++20 数据库 RPC 服务(libmsg + SQLite)
db 模块的 C++20 重实现,接口与 Python 版完全兼容:相同的 execute 路由、相同的请求/响应格式、相同的 where 语法。gateway 的 DbC::call_msg() 以及所有使用 pymsg execute 路由的调用方零改动即可切换。
与 Python db 的关系
| 特性 | db(Python) | db2(C++) |
|---|---|---|
| 传输协议 | pymsg TCP 帧 | pymsg TCP 帧(libmsg) |
execute 路由接口 |
✅ | ✅ 完全对齐 |
| where 运算符 | ✅ | ✅ 完全对齐 |
| 响应格式 | ✅ | ✅ 完全对齐 |
$now 哨兵值 |
✅ | ✅ |
| confs 配置拉取 | ✅ | ✅ |
| Logs 自动建表 + 清理 | ✅ | ✅ |
| 数据库后端 | SQLite / PG / MySQL | SQLite(直接 C API) |
| 依赖 | Python 3 + SQLAlchemy | libmsg + boost::json + libsqlite3 |
快速开始
编译
./_set.sh # 等同于 make,产物为 build/db2
需要系统已安装:libboost-json-dev、libsqlite3-dev,以及 libmsg 已构建(../libmsg/build/libus_msg.so)。
启动
./_run.sh # 读 confs 自动获取配置
./_run.sh --addr 127.0.0.1 --port 8005 --workers 4 # 覆盖监听参数
服务启动时强依赖 confs(拉取 db.ini)。confs 未就绪时自动重试,不要求启动顺序。
配置管理
与 Python db 完全一致:启动时从 confs 服务拉取 db.ini,失败则退出(exit code 1)。
confs 配置文件(db.ini)
配置文件托管于 confs/configs/db.ini:
[server]
host = 127.0.0.1
port = 8005
workers = 4
[database]
url = sqlite:///database.db
[logs]
retention_days = 30
max_records = 1000000
cleanup_interval = 3600
引导配置(confs 连接地址)
| 来源 | 优先级 |
|---|---|
环境变量 CONFS_HOST / CONFS_PORT |
最高 |
默认值 127.0.0.1:8010 |
兜底 |
环境变量(最高优先级,覆盖 confs 配置值)
| 环境变量 | 说明 | 对应配置项 |
|---|---|---|
LISTEN_ADDR |
监听地址 | [server] host |
LISTEN_PORT |
监听端口 | [server] port |
WORKERS |
工作线程数 | [server] workers |
DATABASE_URL |
数据库文件路径 | [database] url |
CONFS_HOST |
confs 地址(引导) | — |
CONFS_PORT |
confs 端口(引导) | — |
命令行参数(最高优先级)
--addr / -a 监听地址
--port / -p 监听端口
--workers / -w 工作线程数
协议与 API
与 Python db 完全一致,可直接参考 db/README.md。以下为要点摘要。
传输协议(pymsg)
帧格式:[4 字节大端长度][UTF-8 JSON],TCP 长连接。
execute 路由 — 通用 CRUD
{
"_route": "execute",
"action": "select",
"table": "Users",
"columns": ["id", "username"],
"values": {"username": "bob", "password_hash": "..."},
"where": {
"is_active": 1,
"create_at": {"ge": "2026-01-01T00:00:00"}
},
"order_by": [{"column": "id", "desc": true}],
"limit": 10,
"offset": 0
}
where 条件运算符
| 运算符 | SQL | 示例 |
|---|---|---|
| 直接值 | col = ? |
"name": "Alice" |
eq |
= |
"age": {"eq": 30} |
ne |
!= |
"status": {"ne": "deleted"} |
gt |
> |
"score": {"gt": 80} |
ge |
>= |
"score": {"ge": 60} |
lt |
< |
"price": {"lt": 100} |
le |
<= |
"price": {"le": 200} |
like |
LIKE |
"name": {"like": "%Smith%"} |
in |
IN (...) |
"id": {"in": [1,2,3]} |
多条件自动 AND 连接。
响应格式
// 成功
{"success": true, "data": [{"id": 1, "username": "Alice"}]}
// insert
{"success": true, "data": [{"lastrowid": 42}]}
// update / delete
{"success": true, "data": [{"rowcount": 3}]}
// 失败
{"success": false, "error": "table not found: Foo"}
日期时间列
列类型含 DATETIME/DATE/TIME,或列名含 _at/time/date 的列自动视为日期列:
| 写入值 | 行为 |
|---|---|
"2026-07-28T22:30:00" |
原样存储(UTC) |
"$now" |
替换为服务端当前 UTC 时间 |
| 其他字符串 | 原样存储 |
使用示例(C++ gateway)
gateway 的 DbC::call_msg() 无需任何修改,直接指向 db2 端口即可:
// DbC::init("127.0.0.1", 8005); // 不变
// 查询用户
auto result = DbC::call_msg({
{"action", "select"},
{"table", "Users"},
{"where", {{"username", "alice"}}},
{"limit", 1},
});
// 更新登录时间
DbC::call_msg({
{"action", "update"},
{"table", "Users"},
{"values", {{"last_login", "$now"}}},
{"where", {{"id", user_id}}},
});
使用示例(Python pymsg)
from pymsg.rpc import RpcClient
client = RpcClient("127.0.0.1", 8005, "myservice")
# 查询
rows = client.call("execute", {
"action": "select",
"table": "Users",
"where": {"is_active": 1},
"order_by": [{"column": "id", "desc": True}],
"limit": 20,
})
# 插入
result = client.call("execute", {
"action": "insert",
"table": "Notes",
"values": {"pid": 0, "owner": "alice", "title": "hello", "content": "world"},
})
print(result[0]["lastrowid"])
内置日志能力
db2 内置 Logs 表(启动时自动建表 + 索引),设计与 Python db 完全一致。
Logs 表结构
CREATE TABLE IF NOT EXISTS Logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ts TEXT NOT NULL, -- UTC ISO 8601
service TEXT NOT NULL,
level TEXT NOT NULL, -- DEBUG/INFO/WARNING/ERROR/CRITICAL
message TEXT NOT NULL, -- 写入时截断至 8192 字节
trace_id TEXT,
extra TEXT -- 可选 JSON 字符串,截断至 4096 字节
);
-- 索引: (service,ts) / (level,ts) / (ts) / (trace_id)
外部服务通过 execute 路由 INSERT 到 Logs 表即可。可复用 db/example_log_client.py。
自动清理
后台线程每 cleanup_interval 秒(默认 3600)执行:
- 删除超过
retention_days天(默认 30)的记录 - 若总条数超过
max_records(默认 100 万),删除最旧的超出部分
安全机制
- 表名仅允许字母、数字、下划线,防止 SQL 注入
- 所有 values / where 值通过
sqlite3_bind_text参数绑定,无字符串拼接 - 仅操作已有表(
PRAGMA table_info反射),无法建表或删表 - RpcServer 异常自动脱敏(过滤内部路径),错误细节仅写 stderr
实现说明
- per-thread 连接:每个 worker 线程持有独立
sqlite3*连接(thread_local),避免跨线程共享 prepared statement - 列 schema 缓存:
PRAGMA table_info结果加锁缓存,仅首次请求时反射 - SQLite PRAGMA:
journal_mode=WAL、synchronous=NORMAL、foreign_keys=ON、busy_timeout=5000、mmap_size=268435456,对齐 Python 版配置
文件说明
| 文件 | 说明 |
|---|---|
src/db2.cpp |
全部实现(配置/连接池/路由/日志/清理) |
Makefile |
编译规则,链接 libus_msg + boost_json + sqlite3 |
_set.sh |
一键编译 |
_run.sh |
启动服务 |
docker-entrypoint.sh |
容器入口:初始化 schema + 启动 |