Commit 0320cbc0 authored by Data Governance Dev's avatar Data Governance Dev

fix(idcard): Oracle 反查 3 个串联 bug

主题 16 — Oracle 反查 0 行
  - 现象:Oracle 扫描有违规值但反查表格 0 行
  - 根因:data_dict 的 column_name 是 Oracle 原样「大写」,但
    _rows_as_dicts 把行 dict key 规范化成「小写」,
    row.get('TZRSFZH') 永远 None → 现场 validate 误判 NULL 跳过
  - 修复:_fetch_bad_rows_one 加 row.get(cname.lower()) fallback

主题 17 — Oracle 表格其他列也空
  - 现象:DA60_QY_MSXX 70 行违规展开后只有「违规」列,其他列全空
  - 根因:Oracle _rows_as_dicts 把所有列名强制小写,data_dict 大写,
    前端 row[colName](大写)查不到 → formatCell(undefined)='∅'
    主题 16 只修了 cname 单列,没修其他列
  - 用户反馈:「mysql 和 Oracle 难道不是用的同一套接口吗?」
    三个 DB 走同一个 _rows_as_dicts,但驱动 case 行为不同:
      MySQL pymysql: 跟 schema
      达梦 dmPython: 保留原样
      Oracle oracledb: 总是 lowercase
  - 修复:新增 _normalize_row_keys(row, columns),装配 BadTable 时
    用 columns 元数据做大小写权威,把 row 所有 key 改成一致

主题 18 — 0 违规的表不显示
  - 现象:PM_SYSTEMUSER.SFZH 0 违规但表格仍渲染 + 空表 + 「该表无违规行」
  - 根因:装配点不管有没有违规都创建 BadTable
  - 修复:流式推送处 + 最终汇总处都加 if not fixed_rows: continue

详细 work-log 见 work-logs/2026-08-17.md 主题 16/17/18
parent 09998006
......@@ -457,7 +457,34 @@ def _scan_one_column(
progress.finish_one()
# ── 单列反查完整行 worker(在子线程里跑)──────────────────────
# ── row keys 大小写规范化(2026-08-17 主题 17)─────────────────
def _normalize_row_keys(row: dict, columns: list[dict]) -> dict:
"""把 row 的 key 规范化成跟 columns 元数据一致的大小写。
背景:Oracle 驱动的 _rows_as_dicts 把所有列名强制小写,但
all_tab_columns 返回的 COLUMN_NAME 是大写。前端表格按
row[colName](大写)查不到值 → 看似「表格只有表头没有数据」。
MySQL pymysql 的 DictCursor 按 schema 大小写返回(与 data_dict 一致),
达梦类似,所以这个函数对它们是 no-op;只对 Oracle 起作用。
实现:构建 {col.lower(): col.original} 的反向索引,把 row 里
所有大小写不匹配的 key 改写成 columns 元数据里的原始 case。
内部字段(_id_card_violation 等)原样保留。
"""
if not columns:
return row
case_map = {c["column_name"].lower(): c["column_name"] for c in columns if c.get("column_name")}
out = {}
for k, v in row.items():
if k.startswith("_"):
out[k] = v # 内部字段保持原样
else:
canonical = case_map.get(k.lower(), k)
out[canonical] = v
return out
def _fetch_bad_rows_one(
cfg_dict: dict,
tname: str,
......@@ -471,14 +498,23 @@ def _fetch_bad_rows_one(
rows_with_reason 每行 = 原数据库行 + '_id_card_violation' 字段(违规原因)
2026-08-17 修复:现场再 validate 一次,不再依赖 bad_reasons key 匹配
- 背景:MySQL DISTINCT / IN 按 collation 比较(utf8mb4_unicode_ci 不区分
大小写),但 Python dict 区分大小写
- 旧逻辑:如果数据库真实存的是大写 X 但 distinct 返回小写 x,
bad_reasons 里只有 '42112319760324521x',反查拿到大写 X 的行就
查不到 key,错误地标 '不合规'
- 新逻辑:对每行现场 validate 一次 —— 合规行直接丢弃,违规行给出准确原因
(避免 collation / 大小写 / 全角半角等任何键不匹配导致的误标)
2026-08-17 两轮修复:
① 现场再 validate 一次,不再依赖 bad_reasons key 匹配
- 背景:MySQL DISTINCT / IN 按 collation 比较(utf8mb4_unicode_ci 不区分
大小写),但 Python dict 区分大小写
- 旧逻辑:如果数据库真实存的是大写 X 但 distinct 返回小写 x,
bad_reasons 里只有 '42112319760324521x',反查拿到大写 X 的行就
查不到 key,错误地标 '不合规'
- 新逻辑:对每行现场 validate 一次 —— 合规行直接丢弃,违规行给出准确原因
② row.get(cname) 列名大小写兼容
- 背景:data_dict 里的 column_name 是 Oracle 原样返回「大写」,
但 _rows_as_dicts 把行 dict 的 key 规范化成「小写」,于是
row.get('TZRSFZH') 永远拿到 None(实际 key 是 'tzrsfzh')。
- 旧行为:v=None → v_str='' → bad_reasons.get('', '不合规') → 加进去,
所以表面看「有数据」(带 '不合规' fallback)
- 现场 validate 后:v=None → if v is None: continue → 整行被丢掉,
于是 Oracle 全表 0 行(看似表格为空)
- 修复:大小写不敏感地找 row.get(cname) / row.get(cname.lower())
"""
progress.start_column(tname, cname)
cfg = DBConfig(**cfg_dict)
......@@ -493,7 +529,10 @@ def _fetch_bad_rows_one(
enriched = []
skipped_ok = 0
for row in rows:
# ★ 大小写兼容 —— 见 docstring 修复 ②
v = row.get(cname)
if v is None:
v = row.get(cname.lower())
if v is None:
# NULL 不算违规(2026-08-17 起空白/None/全空格直接合规)
continue
......@@ -672,12 +711,12 @@ def _run_scan_in_thread(
# ── 流式推送:这张表的所有列都反查完 → 立刻 push ──
if pending_per_table[tname] <= 0:
bad_rows = [dict(kv) for kv in rec["bad_rows_set"]]
fixed_rows = []
for row in bad_rows:
r2 = {}
for k, v in row.items():
r2[k] = v
fixed_rows.append(r2)
# 规范化 row keys 大小写(Oracle 驱动小写 → columns 元数据大写)
fixed_rows = [_normalize_row_keys(row, rec["columns"]) for row in bad_rows]
# 0 违规的表不推送(前端不展示空表)—— 主题 18
if not fixed_rows:
logger.info(f" · ⏭ {tname} 反查完但 0 违规,跳过推送")
continue
bt = BadTable(
table_name=tname,
table_comment=rec["table_comment"],
......@@ -694,12 +733,12 @@ def _run_scan_in_thread(
total_bad_rows = 0
for tname, rec in violations_by_table_dict.items():
bad_rows = [dict(kv) for kv in rec["bad_rows_set"]]
fixed_rows = []
for row in bad_rows:
r2 = {}
for k, v in row.items():
r2[k] = v
fixed_rows.append(r2)
# 规范化 row keys 大小写(Oracle 驱动小写 → columns 元数据大写)
fixed_rows = [_normalize_row_keys(row, rec["columns"]) for row in bad_rows]
# 0 违规的表跳过 —— 主题 18
if not fixed_rows:
logger.info(f" · ⏭ 最终汇总时跳过 {tname}(0 违规)")
continue
violations_by_table.append(BadTable(
table_name=tname,
table_comment=rec["table_comment"],
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment