verify_import_and_schema.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. #!/usr/bin/env python3
  2. """Verify key table row counts and compare backup vs 165 schema for failed imports."""
  3. from __future__ import annotations
  4. import re
  5. from pathlib import Path
  6. import pymysql
  7. SQL_PATH = Path(
  8. r"D:\Projects\Ai-DOP\阿里云数据库备份\aidopdev_20260608_053527.sql\aidopdev_20260608_053527.sql"
  9. )
  10. DB = dict(
  11. host="123.60.180.165",
  12. port=3306,
  13. user="aidopremote",
  14. password="1234567890aiDOP#",
  15. database="aidopdev",
  16. charset="utf8mb4",
  17. )
  18. FAILED_TABLES = [
  19. "itemmaster",
  20. "prioritycode",
  21. "purordrctdetail",
  22. "purordrctmaster",
  23. "scm_jhjh_jq",
  24. "scm_shd",
  25. "scm_shdzb",
  26. "nbrdayinfo",
  27. ]
  28. KEY_TABLES = [
  29. "sysuser",
  30. "systenant",
  31. "ado_smart_ops_kpi_master",
  32. "ado_smart_ops_layout_item",
  33. "ado_smart_ops_dashboard_widget",
  34. "ado_smart_ops_dashboard_page_config",
  35. "mdp_stg_item",
  36. "mdp_std_item",
  37. "mdp_stg_supplier",
  38. "mdp_stg_purchase_order",
  39. "mdp_stg_work_order_material",
  40. "ic_item",
  41. "dwd_process_outsource_delivery",
  42. "abpauditlogs",
  43. "approvalflowtask",
  44. "syslogop",
  45. "sysmenu",
  46. "sysrole",
  47. ]
  48. def normalize(name: str) -> str:
  49. return name.strip("`").lower()
  50. def backup_create_columns(text: str, table: str) -> list[str]:
  51. match = re.search(
  52. rf"CREATE TABLE `{re.escape(table)}`\s*\((.*?)\)\s*ENGINE",
  53. text,
  54. re.IGNORECASE | re.DOTALL,
  55. )
  56. if not match:
  57. return []
  58. cols = []
  59. for line in match.group(1).splitlines():
  60. line = line.strip()
  61. if line.startswith("`"):
  62. cols.append(line.split("`", 2)[1])
  63. return cols
  64. def first_insert_info(text: str, table: str) -> tuple[str | None, int | None, list[str] | None]:
  65. pattern = re.compile(rf"^INSERT INTO `{re.escape(table)}`(.+?);", re.MULTILINE | re.DOTALL)
  66. match = pattern.search(text)
  67. if not match:
  68. return None, None, None
  69. tail = match.group(1).strip()
  70. named_cols: list[str] | None = None
  71. if tail.startswith("(") and ")" in tail:
  72. head, rest = tail[1:].split(")", 1)
  73. if "VALUES" not in head.upper():
  74. named_cols = [c.strip().strip("`") for c in head.split(",")]
  75. tail = rest.strip()
  76. values_idx = tail.upper().find("VALUES")
  77. if values_idx < 0:
  78. return tail[:120], None, named_cols
  79. values_part = tail[values_idx + 6 :].strip()
  80. if not values_part.startswith("("):
  81. return tail[:120], None, named_cols
  82. depth = 0
  83. in_str = False
  84. esc = False
  85. end = 0
  86. for i, ch in enumerate(values_part):
  87. if esc:
  88. esc = False
  89. continue
  90. if ch == "\\" and in_str:
  91. esc = True
  92. continue
  93. if ch == "'":
  94. in_str = not in_str
  95. continue
  96. if in_str:
  97. continue
  98. if ch == "(":
  99. depth += 1
  100. elif ch == ")":
  101. depth -= 1
  102. if depth == 0:
  103. end = i + 1
  104. break
  105. first_tuple = values_part[:end]
  106. inner = first_tuple[1:-1]
  107. vals: list[str] = []
  108. cur: list[str] = []
  109. depth = 0
  110. in_str = False
  111. esc = False
  112. for ch in inner:
  113. if esc:
  114. cur.append(ch)
  115. esc = False
  116. continue
  117. if ch == "\\" and in_str:
  118. cur.append(ch)
  119. esc = True
  120. continue
  121. if ch == "'":
  122. in_str = not in_str
  123. cur.append(ch)
  124. continue
  125. if in_str:
  126. cur.append(ch)
  127. continue
  128. if ch == "(":
  129. depth += 1
  130. cur.append(ch)
  131. continue
  132. if ch == ")":
  133. depth -= 1
  134. cur.append(ch)
  135. continue
  136. if ch == "," and depth == 0:
  137. vals.append("".join(cur).strip())
  138. cur = []
  139. continue
  140. cur.append(ch)
  141. if cur:
  142. vals.append("".join(cur).strip())
  143. return tail[:120], len(vals), named_cols
  144. def resolve_table(cur, lower_name: str) -> str | None:
  145. cur.execute(
  146. "SELECT TABLE_NAME FROM information_schema.TABLES "
  147. "WHERE TABLE_SCHEMA = DATABASE() AND LOWER(TABLE_NAME) = %s",
  148. (lower_name,),
  149. )
  150. row = cur.fetchone()
  151. return row[0] if row else None
  152. def target_columns(cur, table: str) -> list[str]:
  153. cur.execute(
  154. "SELECT COLUMN_NAME FROM information_schema.COLUMNS "
  155. "WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s "
  156. "ORDER BY ORDINAL_POSITION",
  157. (table,),
  158. )
  159. return [r[0] for r in cur.fetchall()]
  160. def main() -> None:
  161. text = SQL_PATH.read_text(encoding="utf-8", errors="replace")
  162. conn = pymysql.connect(**DB)
  163. cur = conn.cursor()
  164. print("=== Key business table row counts (165) ===")
  165. for lower in KEY_TABLES:
  166. actual = resolve_table(cur, lower)
  167. if not actual:
  168. print(f" {lower}: MISSING")
  169. continue
  170. cur.execute(f"SELECT COUNT(*) FROM `{actual}`")
  171. print(f" {actual}: {cur.fetchone()[0]:,}")
  172. print("\n=== Failed import tables: schema diff ===")
  173. for lower in FAILED_TABLES:
  174. actual = resolve_table(cur, lower)
  175. backup_cols = backup_create_columns(text, lower)
  176. _, val_count, named_cols = first_insert_info(text, lower)
  177. target_cols = target_columns(cur, actual) if actual else []
  178. print(f"\n[{lower} -> {actual or 'MISSING'}]")
  179. print(f" backup CREATE columns: {len(backup_cols)}")
  180. print(f" backup INSERT value count (1st row): {val_count}")
  181. if named_cols:
  182. print(f" backup INSERT named columns ({len(named_cols)}): {named_cols[:8]}...")
  183. print(f" target columns: {len(target_cols)}")
  184. if backup_cols and target_cols:
  185. backup_set = {c.lower() for c in backup_cols}
  186. target_set = {c.lower() for c in target_cols}
  187. only_backup = sorted(backup_set - target_set)
  188. only_target = sorted(target_set - backup_set)
  189. if only_backup:
  190. print(f" only in backup: {only_backup[:12]}{'...' if len(only_backup)>12 else ''}")
  191. if only_target:
  192. print(f" only on 165: {only_target[:12]}{'...' if len(only_target)>12 else ''}")
  193. conn.close()
  194. if __name__ == "__main__":
  195. main()