# 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 | ## 快速开始 ### 编译 ```bash ./_set.sh # 等同于 make,产物为 build/db2 ``` 需要系统已安装:`libboost-json-dev`、`libsqlite3-dev`,以及 `libmsg` 已构建(`../libmsg/build/libus_msg.so`)。 ### 启动 ```bash ./_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](../confs/configs/db.ini): ```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](../db/README.md)。以下为要点摘要。 ### 传输协议(pymsg) 帧格式:`[4 字节大端长度][UTF-8 JSON]`,TCP 长连接。 ### execute 路由 — 通用 CRUD ```json { "_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** 连接。 ### 响应格式 ```json // 成功 {"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 端口即可: ```cpp // 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) ```python 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 表结构 ```sql 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](../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 + 启动 |