feat: 前置检查补全库不存在自动创建(CREATE DATABASE),不再依赖库已存在
This commit is contained in:
+45
-5
@@ -1,12 +1,13 @@
|
||||
"""数据库前置检查:确保各库「必须存在的结构表」已建好,缺表则按 db_init/ 下的初始化 SQL 自动创建。
|
||||
"""数据库前置检查:先确保各「库」存在(不存在则自动创建),再确保各库「必须存在的结构表」已建好。
|
||||
|
||||
约定(见 db_init/README.md):
|
||||
- 初始化 SQL 放在 BASE_DIR/db_init/,文件名形如 ``<库标识>.<表名>.sql``。
|
||||
- 建库:对 celldata 与 capacityreport(仅直连 MySQL 时) 两个目标库执行 CREATE DATABASE IF NOT EXISTS,
|
||||
库名以用户配置为准;连接时若库不存在不会报错(先用不带 database 的连接建库)。
|
||||
- 建表:初始化 SQL 放在 BASE_DIR/db_init/,文件名形如 ``<库标识>.<表名>.sql``,仅当目标表「不存在」时执行,
|
||||
因此 sector_band_ref 这类带预设数据的表只在首次创建时写入,绝不覆盖用户自定义。
|
||||
- 库标识 -> 实际连接(库名以用户配置为准,不写死):
|
||||
celldata -> AppConfig.cell_data.mysql(CellData 库,始终直连 MySQL)
|
||||
capacityreport -> AppConfig.mysql(主仓库,仅当 warehouse_type == 'mysql' 直连时检查)
|
||||
- 仅当目标表「不存在」时执行对应 SQL;已存在则跳过整文件,
|
||||
因此 sector_band_ref 这类带预设数据的表只在首次创建时写入,绝不覆盖用户自定义。
|
||||
- 全过程 best-effort:单个库/单张表失败只记录日志并继续,不阻断启动或处理流程。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
@@ -20,6 +21,9 @@ from app.config import BASE_DIR, AppConfig, MySQLConfig
|
||||
|
||||
DB_INIT_DIR = BASE_DIR / "db_init"
|
||||
|
||||
# 需要确保存在的目标库(建库不依赖是否有建表 SQL;主库表为动态生成,故无建表文件但仍需建库)
|
||||
DB_KEYS = ("celldata", "capacityreport")
|
||||
|
||||
|
||||
def _log(logger, message: str) -> None:
|
||||
if logger is not None:
|
||||
@@ -42,6 +46,40 @@ def _target_mysql(app_config: AppConfig, db_key: str) -> Optional[MySQLConfig]:
|
||||
return None
|
||||
|
||||
|
||||
def _ensure_database(mysql: MySQLConfig) -> None:
|
||||
"""库不存在则创建(用不带 database 的连接执行 CREATE DATABASE IF NOT EXISTS)。"""
|
||||
safe_name = str(mysql.dbname).replace("`", "")
|
||||
conn = pymysql.connect(
|
||||
host=mysql.host,
|
||||
port=mysql.port,
|
||||
user=mysql.user,
|
||||
password=mysql.passwd,
|
||||
charset="utf8mb4",
|
||||
cursorclass=pymysql.cursors.Cursor,
|
||||
autocommit=True,
|
||||
)
|
||||
try:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute(
|
||||
f"CREATE DATABASE IF NOT EXISTS `{safe_name}` "
|
||||
f"CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"
|
||||
)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def _ensure_databases(app_config: AppConfig, logger=None) -> None:
|
||||
"""确保所有目标库存在(best-effort)。"""
|
||||
for db_key in DB_KEYS:
|
||||
mysql = _target_mysql(app_config, db_key)
|
||||
if mysql is None or not mysql.dbname:
|
||||
continue
|
||||
try:
|
||||
_ensure_database(mysql)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
_log(logger, f"[前置检查] 确保库 {db_key}({mysql.dbname}@{mysql.host}:{mysql.port}) 失败:{exc}")
|
||||
|
||||
|
||||
def _connect(mysql: MySQLConfig):
|
||||
return pymysql.connect(
|
||||
host=mysql.host,
|
||||
@@ -86,7 +124,9 @@ def _discover() -> dict[str, list[tuple[str, Path]]]:
|
||||
|
||||
|
||||
def ensure_required_tables(app_config: AppConfig, logger=None) -> None:
|
||||
"""检查各库必须存在的表,缺则按初始化 SQL 建好。失败不抛出(仅记录日志)。"""
|
||||
"""先确保各库存在(不存在则创建),再检查各库必须存在的表,缺则按初始化 SQL 建好。失败不抛出。"""
|
||||
_ensure_databases(app_config, logger)
|
||||
|
||||
groups = _discover()
|
||||
for db_key, items in groups.items():
|
||||
mysql = _target_mysql(app_config, db_key)
|
||||
|
||||
+5
-3
@@ -2,7 +2,8 @@
|
||||
|
||||
本目录存放各数据库「**必须存在的结构表**」的初始化 SQL。程序启动时、以及每次执行
|
||||
CellData 脚本前,会由 `app/db_init.py` 的 `ensure_required_tables()` 自动检查:
|
||||
**某张必需表不存在时,就执行对应的初始化 SQL 把它建好**,从而避免运行过程中因缺表报错。
|
||||
**① 库(schema)不存在时自动创建**(celldata 与 capacityreport,库名以配置为准);
|
||||
**② 某张必需表不存在时,执行对应的初始化 SQL 把它建好**,从而避免运行过程中因缺库/缺表报错。
|
||||
|
||||
## 文件命名规则
|
||||
|
||||
@@ -26,7 +27,8 @@ CellData 脚本前,会由 `app/db_init.py` 的 `ensure_required_tables()` 自
|
||||
- 因此带预设数据的表(如 `sector_band_ref`)**只在首次创建时写入预设**,之后你在库里
|
||||
对它的任何增改都会被保留,不会被覆盖或重置。
|
||||
- 全过程 best-effort:单库连不上 / 单表初始化失败只记日志,不会中断启动或数据处理。
|
||||
- 前提:**数据库本身需已存在**(本机制只建表,不建库)。
|
||||
- **建库**:连接前先用不带 database 的连接执行 `CREATE DATABASE IF NOT EXISTS`,故库不存在也能自愈;
|
||||
前提是 MySQL 账号有建库权限(root 默认有)。Metrix 模式下主库走平台 API,不在此建库。
|
||||
|
||||
## 当前清单
|
||||
|
||||
@@ -38,7 +40,7 @@ CellData 脚本前,会由 `app/db_init.py` 的 `ensure_required_tables()` 自
|
||||
| `celldata.sector_band_ref.sql` | `sector_band_ref` | 频段特征库(频点区间+PLMN→制式/频段),含预设 10 条规则,供逆推用,可自行增改。 |
|
||||
|
||||
### capacityreport(主仓库)
|
||||
当前**无需前置建表**:主仓库的原始表(`4G_UD`/`5G_UD`)、结果表(`4G_结果表`/`5G_结果表`)
|
||||
**会自动建库**(直连 MySQL 模式下),但**无需前置建表**:主仓库的原始表(`4G_UD`/`5G_UD`)、结果表(`4G_结果表`/`5G_结果表`)
|
||||
以及从 CellData 复制过来的 `sector`/`cellinfo`,都由导入流程 / `ReportScript.sql` /
|
||||
跨库复制以 `DROP TABLE IF EXISTS` + `CREATE` 动态生成,结构随源数据字段而定,
|
||||
不宜在此预先固定结构(会与动态建表冲突)。如确有需要,按上面的命名规则新增
|
||||
|
||||
@@ -1024,6 +1024,13 @@ CellData 处理日志细化(`app/services/cell_data.py`):
|
||||
- 准确率(MCP 实测,重叠 66001):网络 100%、制式 97.88%(排除 3DMM 即 100%)、频段 97.6%、带宽 99.75%、站型 99.94%、**物理站 99.59%、扇区 99.20%**;残差为不可还原的人工差异(室分多载波编号、700M 个别人工编号、源数据制表符等)。详见 `docs/sector_inference_research.md`。
|
||||
- 关键坑:MySQL 字符串字面量会吞掉未知转义的反斜杠,正则字面量需写 `\\(`(.sql 文件)/ JSON 调用需 `\\\\(`;ICU 正则字符类内的 `[ ]` 易误判,统一把名称中的 `[]` 转 `()` 后只处理圆括号。
|
||||
|
||||
## 2026-06-26:前置检查补全「库不存在自动创建」
|
||||
|
||||
- 问题:原 db_init 只建表不建库,库(schema)不存在时连接 `database=dbname` 直接失败,库始终建不出来。
|
||||
- 修复:`app/db_init.py` 新增 `_ensure_database`(用**不带 database** 的连接执行 `CREATE DATABASE IF NOT EXISTS \`库\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`)与 `_ensure_databases`(遍历 `DB_KEYS=(celldata, capacityreport)`,主库仅直连 MySQL 模式);`ensure_required_tables` 开头先建库再建表。建库走独立库清单**不依赖建表 SQL**(主库无建表文件但仍需建库)。库名以配置为准、反引号去冲突;best-effort 不阻断。
|
||||
- 调用链不变:main 启动 lifespan + execute_celldata_script 前均会触发,故启动/手动上传/容量处理前库都会自愈。需 MySQL 账号有建库权限(root 默认有);Metrix 模式主库走平台不在此建。
|
||||
- 校验:py_compile 通过;MCP 验证 `CREATE DATABASE IF NOT EXISTS` 语法/权限有效(建临时库→确认→删除)。db_init/README.md 同步更正(原写"只建表不建库")。
|
||||
|
||||
## 2026-06-26:修复看板问题清单固定列重叠(透明背景透出下层列)
|
||||
|
||||
- 现象:问题小区清单横向滚动时,右固定列「问题」与滚动区「日均流量」列视觉重叠。
|
||||
|
||||
Reference in New Issue
Block a user