_tmp_parse_field_level.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. # -*- coding: utf-8 -*-
  2. """Extract UPDATE SET column hints and key business assignments from procs."""
  3. from __future__ import annotations
  4. import json
  5. import re
  6. from pathlib import Path
  7. import pyodbc
  8. OUT = Path(__file__).with_name("_tmp_field_level_io.json")
  9. CONN = (
  10. "DRIVER={ODBC Driver 17 for SQL Server};"
  11. "SERVER=123.60.180.165,1433;DATABASE=dopdemorq;"
  12. "UID=dopsa;PWD=5h3n9)uN;TrustServerCertificate=yes"
  13. )
  14. PROCS = [
  15. # WMS path
  16. "pr_WMS_SavePrepareMaterial",
  17. "pr_WMS_SaveWorkOrdMaterialIssue",
  18. "pr_WMS_SaveWorkOrdMaterialIssueByQty",
  19. "pr_WMS_SaveWorkOrdMaterialConfirm",
  20. "pr_WMS_BPM_SaveCancelPrepareMaterial",
  21. "pr_WMS_CreateBarCodeCard",
  22. "pr_SFM_InventoryTransactionProcessing2",
  23. # MES path
  24. "pr_SFM_SaveWorkOrdChooseByOpData",
  25. "pr_SFM_SaveWorkOrdChooseByRWData",
  26. "pr_SFM_SaveEmployeeWorkStatus",
  27. "pr_SFM_LinePlanedStart",
  28. "pr_SFM_LinePlanedEnd",
  29. "pr_SFM_RejectsTransactionProcessingByOp",
  30. "pr_SFM_InventoryMesTransactionProcessingByOp",
  31. "pr_SFM_InventoryMesCartonTransactionProcessingByOp",
  32. "pr_SFM_EndWorkOrdByOp",
  33. "pr_SFM_AccPeriodSequenceDet",
  34. "pr_MES_UpdateWorkOrder",
  35. "pr_MES_CloseWorkOrders",
  36. ]
  37. # UPDATE dbo.Table SET a=..., b=...
  38. UPD_BLOCK = re.compile(
  39. r"(?is)\bUPDATE\s+(?:\[?dbo\]?\.)?\[?([A-Za-z_][A-Za-z0-9_]*)\]?\s+SET\s+(.*?)(?:\bWHERE\b|\bFROM\b|;|$)"
  40. )
  41. # INSERT INTO Table (cols)
  42. INS_COLS = re.compile(
  43. r"(?is)\bINSERT\s+(?:INTO\s+)?(?:\[?dbo\]?\.)?\[?([A-Za-z_][A-Za-z0-9_]*)\]?\s*\(([^)]{1,800})\)"
  44. )
  45. EXEC_RE = re.compile(
  46. r"(?i)\b(?:EXEC|EXECUTE)\s+(?:\[?dbo\]?\.)?\[?([A-Za-z_][A-Za-z0-9_]*)\]?"
  47. )
  48. # assignment-like mentions Table.Col =
  49. ASSIGN = re.compile(
  50. r"(?i)\b([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)\s*="
  51. )
  52. CORE = {
  53. "WorkOrdMaster",
  54. "WorkOrdRouting",
  55. "WorkOrdDetail",
  56. "PeriodSequenceDet",
  57. "NbrMaster",
  58. "NbrDetail",
  59. "MissedPrint",
  60. "InvMaster",
  61. "InvTransHist",
  62. "LocationDetail",
  63. "LocationMaster",
  64. "LineMaster",
  65. "LineStatusDet",
  66. "LineRunRestDet",
  67. "EmployeeStatus",
  68. "EmpWorkHist",
  69. "EmpWorkWmsHist",
  70. "WorkOrdIssued",
  71. "ReleaseDet",
  72. "SingleBarCode",
  73. "MobileTask",
  74. "PurOrdDetail",
  75. "PurOrdRctDetail",
  76. "ItemMaster",
  77. }
  78. def split_set_cols(set_clause: str) -> list[str]:
  79. cols = []
  80. # rough split by comma at top level
  81. depth = 0
  82. buf = []
  83. for ch in set_clause:
  84. if ch == "(":
  85. depth += 1
  86. elif ch == ")":
  87. depth = max(0, depth - 1)
  88. if ch == "," and depth == 0:
  89. part = "".join(buf).strip()
  90. if part:
  91. cols.append(part)
  92. buf = []
  93. else:
  94. buf.append(ch)
  95. part = "".join(buf).strip()
  96. if part:
  97. cols.append(part)
  98. names = []
  99. for p in cols:
  100. m = re.match(r"\[?([A-Za-z_][A-Za-z0-9_]*)\]?\s*=", p.strip())
  101. if m:
  102. names.append(m.group(1))
  103. return names
  104. def main() -> None:
  105. conn = pyodbc.connect(CONN, timeout=90)
  106. cur = conn.cursor()
  107. result = {}
  108. for name in PROCS:
  109. cur.execute("SELECT OBJECT_DEFINITION(OBJECT_ID(?))", name)
  110. row = cur.fetchone()
  111. text = row[0] if row and row[0] else ""
  112. if not text:
  113. # fuzzy search
  114. cur.execute(
  115. "SELECT name FROM sys.procedures WHERE name LIKE ?",
  116. f"%{name.replace('pr_WMS_', '').replace('pr_SFM_', '')}%",
  117. )
  118. alts = [r[0] for r in cur.fetchall()[:8]]
  119. result[name] = {"empty": True, "alts": alts}
  120. continue
  121. updates = {}
  122. for m in UPD_BLOCK.finditer(text):
  123. tbl, clause = m.group(1), m.group(2)
  124. if tbl not in CORE and not tbl[:1].isupper():
  125. continue
  126. cols = split_set_cols(clause)
  127. updates.setdefault(tbl, [])
  128. for c in cols:
  129. if c not in updates[tbl]:
  130. updates[tbl].append(c)
  131. inserts = {}
  132. for m in INS_COLS.finditer(text):
  133. tbl, cols = m.group(1), m.group(2)
  134. clist = [c.strip().strip("[]") for c in cols.split(",") if c.strip()]
  135. inserts.setdefault(tbl, [])
  136. for c in clist:
  137. if re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", c) and c not in inserts[tbl]:
  138. inserts[tbl].append(c)
  139. # filter inserts/updates to interesting
  140. updates = {k: v for k, v in updates.items() if k in CORE or k.startswith(("qms_", "mes_"))}
  141. inserts = {k: v for k, v in inserts.items() if k in CORE or k.startswith(("qms_", "mes_"))}
  142. nested = sorted(
  143. {
  144. n
  145. for n in EXEC_RE.findall(text)
  146. if n.lower().startswith(("pr_", "qms_"))
  147. }
  148. )
  149. # status / qty literals near key tables
  150. snippets = []
  151. for pat in [
  152. r"(?is)UPDATE\s+(?:\[?dbo\]?\.)?\[?WorkOrdMaster\]?\s+SET.{0,400}",
  153. r"(?is)UPDATE\s+(?:\[?dbo\]?\.)?\[?WorkOrdRouting\]?\s+SET.{0,400}",
  154. r"(?is)UPDATE\s+(?:\[?dbo\]?\.)?\[?PeriodSequenceDet\]?\s+SET.{0,400}",
  155. r"(?is)UPDATE\s+(?:\[?dbo\]?\.)?\[?NbrDetail\]?\s+SET.{0,400}",
  156. r"(?is)UPDATE\s+(?:\[?dbo\]?\.)?\[?NbrMaster\]?\s+SET.{0,400}",
  157. r"(?is)UPDATE\s+(?:\[?dbo\]?\.)?\[?MissedPrint\]?\s+SET.{0,400}",
  158. r"(?is)UPDATE\s+(?:\[?dbo\]?\.)?\[?LineStatusDet\]?\s+SET.{0,400}",
  159. r"(?is)UPDATE\s+(?:\[?dbo\]?\.)?\[?EmployeeStatus\]?\s+SET.{0,400}",
  160. r"(?is)UPDATE\s+(?:\[?dbo\]?\.)?\[?WorkOrdDetail\]?\s+SET.{0,400}",
  161. ]:
  162. for m in re.finditer(pat, text):
  163. s = re.sub(r"\s+", " ", m.group(0))[:350]
  164. snippets.append(s)
  165. if len(snippets) >= 12:
  166. break
  167. if len(snippets) >= 12:
  168. break
  169. result[name] = {
  170. "empty": False,
  171. "def_len": len(text),
  172. "updates": updates,
  173. "inserts": {k: v[:40] for k, v in inserts.items()},
  174. "nested": nested[:25],
  175. "update_snippets": snippets[:12],
  176. }
  177. OUT.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
  178. print("WROTE", OUT)
  179. for name, v in result.items():
  180. if v.get("empty"):
  181. print(name, "EMPTY", v.get("alts"))
  182. continue
  183. print("==", name, "len", v["def_len"])
  184. for t, cols in v["updates"].items():
  185. print(" UPD", t, ":", ", ".join(cols[:25]))
  186. for t, cols in list(v["inserts"].items())[:6]:
  187. print(" INS", t, ":", ", ".join(cols[:15]))
  188. if v["nested"]:
  189. print(" NEST", ", ".join(v["nested"][:8]))
  190. conn.close()
  191. if __name__ == "__main__":
  192. main()