242 lines
9.1 KiB
Python
242 lines
9.1 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
在达梦数据库机器上运行,查看各表结构,用于编写 config 中的 db.query
|
||
用法: python scripts/inspect_dm_tables.py
|
||
|
||
密码获取顺序:环境变量 DM_PASS > 下方 LOCAL_PASSWORD
|
||
若使用 LOCAL_PASSWORD,切勿提交到 git!可执行:git update-index --assume-unchanged scripts/inspect_dm_tables.py
|
||
"""
|
||
import os
|
||
import sys
|
||
|
||
# 可选:在此直接填写密码(仅限本机调试,切勿提交到版本库!)
|
||
LOCAL_PASSWORD = None # 例如: "你的密码"
|
||
|
||
# 支持从项目根或 scripts 目录运行
|
||
_script_dir = os.path.dirname(os.path.abspath(__file__))
|
||
_project_root = os.path.dirname(_script_dir)
|
||
XLSX_PATH = os.path.join(_project_root, "1125暂时属性需求(1).xlsx")
|
||
if _project_root not in sys.path:
|
||
sys.path.insert(0, _project_root)
|
||
|
||
try:
|
||
import dmPython
|
||
except ImportError:
|
||
print("请先安装: pip install dmPython")
|
||
sys.exit(1)
|
||
|
||
CONFIG = {
|
||
"user": os.getenv("DM_USER", "SYSDBA"),
|
||
"password": os.getenv("DM_PASS") or LOCAL_PASSWORD,
|
||
"server": os.getenv("DM_HOST", "127.0.0.1"),
|
||
"port": int(os.getenv("DM_PORT", "9080")),
|
||
"schema": "SYSDBA",
|
||
}
|
||
|
||
TABLES = [
|
||
"OBJECTIVE_INSTANCE",
|
||
"METRIC_VALUE_INSTANCE",
|
||
"METRIC_PROFILE",
|
||
"ROLE_PROFILE",
|
||
"TASK_PROFILE",
|
||
"RELATION_INSTANCE",
|
||
"RES_FORMAL_ANALYSIS",
|
||
]
|
||
|
||
|
||
def _load_required_attrs(xlsx_path: str) -> list:
|
||
"""从 1125暂时属性需求(1).xlsx 解析需求属性列表(一条=一个属性)"""
|
||
if not os.path.exists(xlsx_path):
|
||
print(f" (未找到 {xlsx_path},使用内置属性列表)")
|
||
return _DEFAULT_REQ_ATTRS
|
||
try:
|
||
import pandas as pd
|
||
df = pd.read_excel(xlsx_path, header=None)
|
||
attrs = []
|
||
for _, row in df.iterrows():
|
||
v0 = row[0]
|
||
v2 = str(row[2]) if len(row) > 2 and pd.notna(row[2]) else ""
|
||
if pd.notna(v0) and str(v0).strip() and str(v0) != "属性代码名称":
|
||
attrs.append(str(v0).strip())
|
||
if ":" in v2:
|
||
sub = v2.split(":")[0].strip()
|
||
if sub and sub not in attrs:
|
||
attrs.append(sub)
|
||
return attrs if attrs else _DEFAULT_REQ_ATTRS
|
||
except Exception as e:
|
||
print(f" (解析 xlsx 失败: {e},使用内置属性列表)")
|
||
return _DEFAULT_REQ_ATTRS
|
||
|
||
|
||
# xlsx 解析失败时的兜底属性列表(来自 1125暂时属性需求)
|
||
_DEFAULT_REQ_ATTRS = [
|
||
"f_IC", "f_IA", "f_CS", "f_IT", "f_DP", "f_CPS", "f_CC",
|
||
"position", "effective_radius",
|
||
"response_time", "cycle_time", "data_age", "time_window",
|
||
"core_performance", "survivability", "mtbf",
|
||
"protocol_list", "format_list", "interface_list",
|
||
"security_level", "org_unit", "nation", "history_success",
|
||
]
|
||
|
||
|
||
def main():
|
||
if not CONFIG["password"]:
|
||
print("请设置环境变量 DM_PASS 或在脚本中填写 LOCAL_PASSWORD")
|
||
sys.exit(1)
|
||
|
||
try:
|
||
conn = dmPython.connect(
|
||
user=CONFIG["user"],
|
||
password=CONFIG["password"],
|
||
server=CONFIG["server"],
|
||
port=CONFIG["port"],
|
||
schema=CONFIG["schema"],
|
||
)
|
||
except Exception as e:
|
||
print(f"连接失败: {e}")
|
||
sys.exit(1)
|
||
|
||
cursor = conn.cursor()
|
||
print("=" * 60)
|
||
print("达梦表结构(用于编写 config/db.query)")
|
||
print("=" * 60)
|
||
|
||
for table in TABLES:
|
||
try:
|
||
cursor.execute(f'SELECT * FROM "{CONFIG["schema"]}"."{table}" WHERE 1=0')
|
||
cols = [d[0] for d in cursor.description]
|
||
print(f"\n【{table}】 列: {cols}")
|
||
except Exception as e:
|
||
print(f"\n【{table}】 查询失败: {e}")
|
||
|
||
# 专门查询 METRIC_VALUE_INSTANCE 的字段详情(含完整属性)
|
||
mvi_table = "METRIC_VALUE_INSTANCE"
|
||
print("\n" + "=" * 80)
|
||
print(f"【{mvi_table}】 字段详情(完整属性)")
|
||
print("=" * 80)
|
||
try:
|
||
# 查询 DBA_TAB_COLUMNS 全部常用属性(达梦兼容)
|
||
cursor.execute(
|
||
"""
|
||
SELECT COLUMN_NAME, DATA_TYPE, DATA_LENGTH, DATA_PRECISION, DATA_SCALE,
|
||
NULLABLE, DATA_DEFAULT, COLUMN_ID
|
||
FROM DBA_TAB_COLUMNS
|
||
WHERE OWNER = ? AND TABLE_NAME = ?
|
||
ORDER BY COLUMN_ID
|
||
""",
|
||
(CONFIG["schema"], mvi_table),
|
||
)
|
||
mvi_cols = cursor.fetchall()
|
||
# 表头
|
||
fmt = " {:<18} {:<16} {:<10} {:<10} {:<8} {:<10} {:<20}"
|
||
print(fmt.format("列名", "数据类型", "长度", "精度", "标度", "可空", "默认值"))
|
||
print(" " + "-" * 90)
|
||
for row in mvi_cols:
|
||
col_name, data_type, data_length, data_precision, data_scale, nullable, data_default, col_id = row
|
||
# 类型显示:数值型用 类型(精度,标度),字符型用 类型(长度)
|
||
if data_precision is not None:
|
||
type_str = f"{data_type}({data_precision},{data_scale or 0})"
|
||
elif data_length and data_type.upper() in ("VARCHAR", "CHAR", "VARCHAR2"):
|
||
type_str = f"{data_type}({data_length})"
|
||
elif data_length:
|
||
type_str = f"{data_type}({data_length})"
|
||
else:
|
||
type_str = data_type or ""
|
||
len_val = str(data_length) if data_length is not None else "-"
|
||
prec_val = str(data_precision) if data_precision is not None else "-"
|
||
scale_val = str(data_scale) if data_scale is not None else "-"
|
||
null_str = "NULL" if nullable == "Y" else "NOT NULL"
|
||
default_str = (str(data_default).strip() if data_default else "-")[:18]
|
||
print(fmt.format(col_name, type_str, len_val, prec_val, scale_val, null_str, default_str))
|
||
|
||
# 查询样本数据(若有)
|
||
cursor.execute(f'SELECT * FROM "{CONFIG["schema"]}"."{mvi_table}" WHERE ROWNUM <= 3')
|
||
sample_rows = cursor.fetchall()
|
||
if sample_rows:
|
||
col_names = [d[0] for d in cursor.description]
|
||
print(f"\n 样本数据 (最多 3 行):")
|
||
for i, row in enumerate(sample_rows, 1):
|
||
print(f" 行{i}: {dict(zip(col_names, row))}")
|
||
else:
|
||
print("\n (表无数据)")
|
||
except Exception as e:
|
||
print(f" 查询失败: {e}")
|
||
|
||
# 按 1125暂时属性需求(1).xlsx 查询 METRIC_VALUE_INSTANCE(一条=一个属性)
|
||
print("\n" + "=" * 80)
|
||
print("【METRIC_VALUE_INSTANCE】按需求属性查询(xlsx 中一条 = 一个 METRIC_ID)")
|
||
print("=" * 80)
|
||
try:
|
||
# 1. 从 xlsx 解析需求属性列表
|
||
req_attrs = _load_required_attrs(XLSX_PATH)
|
||
print(f"\n xlsx 需求属性 ({len(req_attrs)} 个): {req_attrs}")
|
||
|
||
# 2. 查询库中实际存在的 METRIC_ID(distinct)
|
||
cursor.execute(
|
||
f'SELECT DISTINCT METRIC_ID FROM "{CONFIG["schema"]}"."METRIC_VALUE_INSTANCE" ORDER BY METRIC_ID'
|
||
)
|
||
db_metric_ids = [r[0] for r in cursor.fetchall()]
|
||
print(f"\n 库中已有 METRIC_ID ({len(db_metric_ids)} 个): {db_metric_ids or '(无数据)'}")
|
||
|
||
# 3. METRIC_PROFILE 对照(METRIC_ID -> 中文名)
|
||
cursor.execute(
|
||
f'SELECT METRIC_ID, METRIC_NAME FROM "{CONFIG["schema"]}"."METRIC_PROFILE"'
|
||
)
|
||
profile_map = dict(cursor.fetchall())
|
||
if profile_map:
|
||
print("\n METRIC_PROFILE 对照:")
|
||
for mid, mname in sorted(profile_map.items()):
|
||
print(f" {mid} -> {mname}")
|
||
|
||
# 4. 交集:需求属性中在库里有的
|
||
in_both = [a for a in req_attrs if a in db_metric_ids]
|
||
# 需求有但库无
|
||
missing = [a for a in req_attrs if a not in db_metric_ids]
|
||
# 库有但需求未列
|
||
extra = [m for m in db_metric_ids if m not in req_attrs]
|
||
|
||
print(f"\n 需求与库交集 (可查): {in_both or '无'}")
|
||
print(f" 需求有但库无: {missing or '无'}")
|
||
print(f" 库有但需求未列: {extra or '无'}")
|
||
|
||
# 5. 按属性查询示例 SQL
|
||
print("\n --- 按属性查询示例 SQL ---")
|
||
for attr in (in_both or req_attrs)[:5]: # 最多展示 5 个
|
||
sql = f'SELECT * FROM "{CONFIG["schema"]}"."METRIC_VALUE_INSTANCE" WHERE METRIC_ID = \'{attr}\''
|
||
print(f" -- {attr}: {sql}")
|
||
if len(in_both or req_attrs) > 5:
|
||
print(f" ... 其余属性同理,将 METRIC_ID = 'xxx' 替换即可")
|
||
except Exception as e:
|
||
print(f" 查询失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
cursor.execute(
|
||
"""
|
||
SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE
|
||
FROM DBA_TAB_COLUMNS
|
||
WHERE OWNER = ?
|
||
ORDER BY TABLE_NAME, COLUMN_ID
|
||
""",
|
||
(CONFIG["schema"],),
|
||
)
|
||
rows = cursor.fetchall()
|
||
|
||
print("\n" + "=" * 60)
|
||
print("所有列详情 (TABLE_NAME | COLUMN_NAME | DATA_TYPE)")
|
||
print("=" * 60)
|
||
cur_table = None
|
||
for table, col, dtype in rows:
|
||
if table != cur_table:
|
||
cur_table = table
|
||
print(f"\n--- {table} ---")
|
||
print(f" {col}: {dtype}")
|
||
|
||
cursor.close()
|
||
conn.close()
|
||
print("\n完成。请根据上述结构在 config 中编写 db.query。")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|