Files
blackbird/db2
qing 00421182e4 fix(db2): 按 JSON 类型分别绑定 SQLite 参数,修复类型丢失
- _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 处理误导)
2026-08-06 18:23:30 +00:00
..
2026-08-06 17:42:50 +00:00

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)执行:

  1. 删除超过 retention_days 天(默认 30)的记录
  2. 若总条数超过 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 + 启动