verify_contract.py 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. """契约一致校验(DB vs API 等价)。
  2. 对同一 mdp_std_* 表,比较 DB 路线批次(sync_batch_id LIKE 前缀 CT_DB_%)与
  3. API 路线批次(CT_API_%)在 source_biz_key 上的集合与行数是否一致。
  4. 用法:
  5. python verify_contract.py --table mdp_std_purchase_receipt
  6. # 自定义批次前缀:
  7. python verify_contract.py --table mdp_std_so --db-prefix CT_DB_ --api-prefix CT_API_
  8. 退出码:0=一致;1=不一致;2=两侧任一为空(未跑入站)。
  9. 前置:DB 路线与 API 路线两次入站都已完成,且分别以 CT_DB_/CT_API_ 前缀写入
  10. mdp_std_*.sync_batch_id(见任务书 §附录 E.2 / §附录 I 回归步骤)。
  11. """
  12. import argparse
  13. import sys
  14. from _db import query
  15. def main():
  16. ap = argparse.ArgumentParser()
  17. ap.add_argument("--table", required=True, help="目标 mdp_std_* 表名")
  18. ap.add_argument("--db-prefix", default="CT_DB_")
  19. ap.add_argument("--api-prefix", default="CT_API_")
  20. ap.add_argument("--key", default="source_biz_key", help="业务键列,默认 source_biz_key")
  21. args = ap.parse_args()
  22. t, k = args.table, args.key
  23. counts = query(
  24. f"""
  25. SELECT 'db' AS src, COUNT(*) AS row_cnt, COUNT(DISTINCT {k}) AS key_cnt
  26. FROM {t} WHERE sync_batch_id LIKE %s
  27. UNION ALL
  28. SELECT 'api', COUNT(*), COUNT(DISTINCT {k})
  29. FROM {t} WHERE sync_batch_id LIKE %s
  30. """,
  31. (args.db_prefix + "%", args.api_prefix + "%"),
  32. )
  33. by = {r["src"]: r for r in counts}
  34. db, api = by.get("db"), by.get("api")
  35. print(f"[counts] db={db} api={api}")
  36. if not db or not api or db["row_cnt"] == 0 or api["row_cnt"] == 0:
  37. print("[FAIL] 某一侧为空:请先分别跑 DB 与 API 入站。", file=sys.stderr)
  38. return 2
  39. only_db = query(
  40. f"""
  41. SELECT {k} AS biz FROM {t} WHERE sync_batch_id LIKE %s
  42. AND {k} NOT IN (SELECT {k} FROM {t} WHERE sync_batch_id LIKE %s)
  43. """,
  44. (args.db_prefix + "%", args.api_prefix + "%"),
  45. )
  46. only_api = query(
  47. f"""
  48. SELECT {k} AS biz FROM {t} WHERE sync_batch_id LIKE %s
  49. AND {k} NOT IN (SELECT {k} FROM {t} WHERE sync_batch_id LIKE %s)
  50. """,
  51. (args.api_prefix + "%", args.db_prefix + "%"),
  52. )
  53. ok = True
  54. if db["key_cnt"] != api["key_cnt"]:
  55. print(f"[FAIL] 键数不等:db={db['key_cnt']} api={api['key_cnt']}", file=sys.stderr)
  56. ok = False
  57. if only_db:
  58. print(f"[FAIL] DB 有 API 无 ({len(only_db)}):{[r['biz'] for r in only_db[:20]]}", file=sys.stderr)
  59. ok = False
  60. if only_api:
  61. print(f"[FAIL] API 有 DB 无 ({len(only_api)}):{[r['biz'] for r in only_api[:20]]}", file=sys.stderr)
  62. ok = False
  63. if ok:
  64. print("[PASS] DB 与 API 路线 source_biz_key 集合与行数一致。")
  65. return 0
  66. return 1
  67. if __name__ == "__main__":
  68. sys.exit(main())