This commit is contained in:
qing committed 2026-08-07 15:47:34 +08:00
commit cc0475e5dc
2 files changed
+234 -38

No files matched your search

+81 -10
View File
@@ -47,6 +47,10 @@ using namespace pmc::net;
static constexpr size_t kMaxMessage = 8192;
static constexpr size_t kMaxExtra = 4096;
// 输入规模限制(防资源耗尽)
static constexpr size_t kMaxWhereConditions = 64; // 单请求 WHERE 条件数上限
static constexpr size_t kMaxInElements = 1000; // 单个 IN 运算符元素上限
// ============================================================================
// 工具:当前 UTC 时间 ISO 8601
// ============================================================================
@@ -151,6 +155,25 @@ static Config load_config(int argc, char* argv[]) {
// 3. 解析 INI(白名单字段)
IniMap ini = parse_ini(ini_text);
// 校验必需配置项(confs 提供的 db.ini 必须包含完整配置)
static const std::pair<const char*, const char*> required_keys[] = {
{"server", "host"},
{"server", "port"},
{"server", "workers"},
{"database", "url"},
{"logs", "retention_days"},
{"logs", "max_records"},
{"logs", "cleanup_interval"},
};
for (auto& [sec, key] : required_keys) {
auto si = ini.find(sec);
if (si == ini.end() || si->second.find(key) == si->second.end()) {
std::fprintf(stderr, "[db2] missing required config: [%s] %s\n", sec, key);
std::exit(1);
}
}
auto get = [&](const std::string& sec, const std::string& key,
const std::string& def = "") -> std::string {
auto si = ini.find(sec);
@@ -243,6 +266,12 @@ struct ColInfo {
std::string type; // 原始类型字符串,大写,如 "DATETIME" "TEXT" "INTEGER"
};
// 表 schema 缓存项,携带 PRAGMA schema_version 以感知 DDL 变更
struct SchemaCacheEntry {
int schema_version;
std::vector<ColInfo> cols;
};
// ============================================================================
// DB2Server
// ============================================================================
@@ -295,7 +324,7 @@ private:
// 表列信息缓存
std::mutex _schema_mtx;
std::unordered_map<std::string, std::vector<ColInfo>> _schema_cache;
std::unordered_map<std::string, SchemaCacheEntry> _schema_cache;
// 后台清理线程
std::atomic<bool> _cleanup_running{false};
@@ -313,15 +342,32 @@ private:
// ---- 列 schema 反射 --------------------------------------------------------
static int _get_schema_version(sqlite3* db) {
sqlite3_stmt* stmt = nullptr;
if (sqlite3_prepare_v2(db, "PRAGMA schema_version", -1, &stmt, nullptr) != SQLITE_OK) {
throw std::runtime_error("PRAGMA schema_version failed");
}
int version = -1;
if (sqlite3_step(stmt) == SQLITE_ROW) {
version = sqlite3_column_int(stmt, 0);
}
sqlite3_finalize(stmt);
return version;
}
std::vector<ColInfo> _get_schema(const std::string& table) {
DbConn& conn = _get_conn();
const int current_version = _get_schema_version(conn.get());
{
std::lock_guard<std::mutex> lk(_schema_mtx);
auto it = _schema_cache.find(table);
if (it != _schema_cache.end()) return it->second;
if (it != _schema_cache.end() && it->second.schema_version == current_version) {
return it->second.cols;
}
}
// PRAGMA table_info 返回: cid, name, type, notnull, dflt_value, pk
DbConn& conn = _get_conn();
std::string sql = "PRAGMA table_info(" + table + ")";
sqlite3_stmt* stmt = nullptr;
if (sqlite3_prepare_v2(conn.get(), sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
@@ -346,8 +392,8 @@ private:
}
std::lock_guard<std::mutex> lk(_schema_mtx);
_schema_cache[table] = cols;
return cols;
_schema_cache[table] = SchemaCacheEntry{current_version, std::move(cols)};
return _schema_cache[table].cols;
}
// 判断列是否为日期时间类型
@@ -414,6 +460,12 @@ private:
WhereClause wc;
std::vector<std::string> parts;
if (where.size() > kMaxWhereConditions) {
throw std::runtime_error("too many where conditions: "
+ std::to_string(where.size())
+ " (max " + std::to_string(kMaxWhereConditions) + ")");
}
// 建立列名 -> ColInfo 映射
std::unordered_map<std::string, ColInfo> col_map;
for (auto& c : cols) col_map[c.name] = c;
@@ -452,6 +504,11 @@ private:
parts.push_back("0");
continue;
}
if (op_val.as_array().size() > kMaxInElements) {
throw std::runtime_error("too many elements in 'in' operator: "
+ std::to_string(op_val.as_array().size())
+ " (max " + std::to_string(kMaxInElements) + ")");
}
std::string placeholders;
for (auto& item : op_val.as_array()) {
if (!placeholders.empty()) placeholders += ", ";
@@ -459,6 +516,8 @@ private:
wc.params.push_back(_coerce_value(ci, item));
}
parts.push_back(col + " IN (" + placeholders + ")");
} else {
throw std::runtime_error("unknown operator: " + op);
}
}
} else {
@@ -559,10 +618,18 @@ private:
}
// limit / offset
if (auto it = req.find("limit"); it != req.end() && it->value().is_int64())
sql += " LIMIT " + std::to_string(it->value().as_int64());
if (auto it = req.find("offset"); it != req.end() && it->value().is_int64())
sql += " OFFSET " + std::to_string(it->value().as_int64());
if (auto it = req.find("limit"); it != req.end() && it->value().is_int64()) {
std::int64_t limit = it->value().as_int64();
if (limit < 0)
throw std::runtime_error("limit must be non-negative");
sql += " LIMIT " + std::to_string(limit);
}
if (auto it = req.find("offset"); it != req.end() && it->value().is_int64()) {
std::int64_t offset = it->value().as_int64();
if (offset < 0)
throw std::runtime_error("offset must be non-negative");
sql += " OFFSET " + std::to_string(offset);
}
sqlite3_stmt* stmt = nullptr;
if (sqlite3_prepare_v2(conn.get(), sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
@@ -611,6 +678,8 @@ private:
DbConn& conn = _get_conn();
const json::object& vals = req.at("values").as_object();
if (vals.empty())
throw std::runtime_error("insert requires non-empty 'values'");
// 建列名 -> ColInfo 映射
std::unordered_map<std::string, ColInfo> col_map;
@@ -653,6 +722,8 @@ private:
DbConn& conn = _get_conn();
const json::object& vals = req.at("values").as_object();
if (vals.empty())
throw std::runtime_error("update requires non-empty 'values'");
std::unordered_map<std::string, ColInfo> col_map;
for (auto& c : cols) col_map[c.name] = c;
@@ -790,7 +861,7 @@ private:
// 1. 删除超过 retention_days 的记录
std::string cutoff_sql =
"DELETE FROM Logs WHERE ts < datetime('now', '-"
"DELETE FROM Logs WHERE ts < strftime('%Y-%m-%dT%H:%M:%S','now', '-"
+ std::to_string(_cfg.retention_days) + " days')";
conn.exec(cutoff_sql);
int expired = sqlite3_changes(conn.get());