ddl_parser.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  1. from __future__ import annotations
  2. import io
  3. import json
  4. import logging
  5. import re
  6. import time
  7. from typing import Any
  8. import requests
  9. from flask import current_app
  10. logger = logging.getLogger(__name__)
  11. class DDLParser:
  12. def __init__(self, api_key=None, timeout=60, max_retries=3):
  13. """
  14. 初始化DDL解析器
  15. 参数:
  16. api_key: LLM API密钥,如果未提供,将从应用配置或环境变量中获取
  17. timeout: API请求超时时间(秒),默认60秒
  18. max_retries: 最大重试次数,默认3次
  19. """
  20. # 如果在Flask应用上下文中,则从应用配置获取参数
  21. self.api_key = api_key or current_app.config.get("LLM_API_KEY")
  22. self.base_url = current_app.config.get("LLM_BASE_URL")
  23. self.model_name = current_app.config.get("LLM_MODEL_NAME")
  24. self.timeout = timeout
  25. self.max_retries = max_retries
  26. self.headers = {
  27. "Authorization": f"Bearer {self.api_key}",
  28. "Content-Type": "application/json",
  29. }
  30. def _make_llm_request(self, payload, operation_name="LLM请求"):
  31. """
  32. 发送LLM请求,支持自动重试
  33. 参数:
  34. payload: 请求payload
  35. operation_name: 操作名称,用于日志
  36. 返回:
  37. API响应结果
  38. """
  39. last_error = None
  40. for attempt in range(self.max_retries):
  41. try:
  42. if attempt > 0:
  43. wait_time = 2**attempt # 指数退避: 2, 4, 8秒
  44. logger.info(
  45. f"{operation_name} 第{attempt + 1}次重试,等待{wait_time}秒..."
  46. )
  47. time.sleep(wait_time)
  48. logger.info(
  49. f"{operation_name} 尝试 {attempt + 1}/{self.max_retries},超时时间: {self.timeout}秒"
  50. )
  51. response = requests.post(
  52. f"{self.base_url}/chat/completions",
  53. headers=self.headers,
  54. json=payload,
  55. timeout=self.timeout,
  56. )
  57. response.raise_for_status()
  58. result = response.json()
  59. logger.info(f"{operation_name} 成功")
  60. return result
  61. except requests.Timeout as e:
  62. last_error = f"请求超时(超过{self.timeout}秒): {str(e)}"
  63. logger.warning(f"{operation_name} 超时: {str(e)}")
  64. except requests.RequestException as e:
  65. last_error = f"API请求失败: {str(e)}"
  66. logger.warning(f"{operation_name} 失败: {str(e)}")
  67. except Exception as e:
  68. last_error = f"未知错误: {str(e)}"
  69. logger.error(f"{operation_name} 异常: {str(e)}")
  70. break # 对于非网络错误,不重试
  71. # 所有重试都失败
  72. logger.error(f"{operation_name} 在{self.max_retries}次尝试后失败: {last_error}")
  73. return None
  74. def parse_ddl(self, sql_content):
  75. """
  76. 解析DDL语句,返回标准化的结构
  77. 参数:
  78. sql_content: 要解析的DDL语句
  79. 返回:
  80. 解析结果的JSON对象
  81. """
  82. prompt = self._optimize_ddl_prompt()
  83. payload = {
  84. "model": self.model_name,
  85. "messages": [
  86. {
  87. "role": "system",
  88. "content": "你是一个专业的SQL DDL语句解析专家,擅长从DDL语句中提取表结构信息并转换为结构化的JSON格式。",
  89. },
  90. {"role": "user", "content": f"{prompt}\n\n{sql_content}"},
  91. ],
  92. }
  93. try:
  94. result = self._make_llm_request(payload, "DDL解析")
  95. if not result:
  96. return {
  97. "code": 500,
  98. "message": f"API请求失败: 在{self.max_retries}次尝试后仍然失败",
  99. }
  100. if "choices" in result and len(result["choices"]) > 0:
  101. content = result["choices"][0]["message"]["content"]
  102. try:
  103. json_match = re.search(r"```json\s*([\s\S]*?)\s*```", content)
  104. if json_match:
  105. json_content = json_match.group(1)
  106. else:
  107. json_content = content
  108. parsed_result = json.loads(json_content)
  109. return parsed_result
  110. except json.JSONDecodeError as e:
  111. return {
  112. "code": 500,
  113. "message": f"无法解析返回的JSON: {str(e)}",
  114. "original_response": content,
  115. }
  116. return {
  117. "code": 500,
  118. "message": "无法获取有效响应",
  119. "original_response": result,
  120. }
  121. except Exception as e:
  122. logger.error(f"DDL解析异常: {str(e)}")
  123. return {"code": 500, "message": f"解析失败: {str(e)}"}
  124. def parse_db_conn_str(self, conn_str):
  125. """
  126. 解析数据库连接字符串
  127. 参数:
  128. conn_str: 要解析的数据库连接字符串
  129. 返回:
  130. 解析结果的JSON对象
  131. """
  132. prompt = self._optimize_connstr_parse_prompt()
  133. payload = {
  134. "model": self.model_name,
  135. "messages": [
  136. {
  137. "role": "system",
  138. "content": "你是一个专业的数据库连接字符串解析专家,擅长解析各种数据库的连接字符串并提取关键信息。",
  139. },
  140. {"role": "user", "content": f"{prompt}\n\n{conn_str}"},
  141. ],
  142. }
  143. try:
  144. result = self._make_llm_request(payload, "连接字符串解析")
  145. if not result:
  146. return {
  147. "code": 500,
  148. "message": f"API请求失败: 在{self.max_retries}次尝试后仍然失败",
  149. }
  150. if "choices" in result and len(result["choices"]) > 0:
  151. content = result["choices"][0]["message"]["content"]
  152. try:
  153. json_match = re.search(r"```json\s*([\s\S]*?)\s*```", content)
  154. if json_match:
  155. json_content = json_match.group(1)
  156. else:
  157. json_content = content
  158. parsed_result = json.loads(json_content)
  159. return parsed_result
  160. except json.JSONDecodeError as e:
  161. return {
  162. "code": 500,
  163. "message": f"无法解析返回的JSON: {str(e)}",
  164. "original_response": content,
  165. }
  166. return {
  167. "code": 500,
  168. "message": "无法获取有效响应",
  169. "original_response": result,
  170. }
  171. except Exception as e:
  172. logger.error(f"连接字符串解析异常: {str(e)}")
  173. return {"code": 500, "message": f"解析失败: {str(e)}"}
  174. def _optimize_ddl_prompt(self):
  175. """返回优化后的提示词模板"""
  176. return """
  177. 请解析以下DDL建表语句,并按照指定的JSON格式返回结果:
  178. 规则说明:
  179. 1. 从DDL语句中识别所有表,可能会有多个表。将所有表放在一个数组中返回。
  180. 2. 表的英文名称(name_en)使用原始大小写,不要转换为小写。
  181. 3. 表的中文名称(name_zh)提取规则:
  182. - 优先从COMMENT ON TABLE语句中提取
  183. - 如果没有注释,则name_zh为空字符串
  184. - 中文名称中不要出现标点符号、"主键"、"外键"、"索引"等字样
  185. 4. 对于每个表,提取所有字段信息到columns数组中,每个字段包含:
  186. - name_zh: 字段中文名称(从COMMENT ON COLUMN提取,如果没有注释则翻译英文名,如果是无意义缩写则为空)
  187. - name_en: 字段英文名称(保持原始大小写)
  188. - data_type: 数据类型(包含长度信息,如VARCHAR(22))
  189. - is_primary: 是否主键("是"或"否",从PRIMARY KEY约束判断)
  190. - comment: 注释内容(从COMMENT ON COLUMN提取完整注释,如果没有则为空字符串)
  191. - nullable: 是否可为空("是"或"否",从NOT NULL约束判断,默认为"是")
  192. 5. 中文字段名不要出现逗号、"主键"、"外键"、"索引"等字样。
  193. 6. 返回格式(使用数组支持多表):
  194. [
  195. {
  196. "table_info": {
  197. "name_zh": "科室对照表",
  198. "name_en": "TB_JC_KSDZB"
  199. },
  200. "columns": [
  201. {
  202. "name_zh": "医疗机构代码",
  203. "name_en": "YLJGDM",
  204. "data_type": "VARCHAR(22)",
  205. "is_primary": "是",
  206. "comment": "医疗机构代码,复合主键",
  207. "nullable": "否"
  208. },
  209. {
  210. "name_zh": "HIS科室代码",
  211. "name_en": "HISKSDM",
  212. "data_type": "CHAR(20)",
  213. "is_primary": "是",
  214. "comment": "HIS科室代码,主键、唯一",
  215. "nullable": "否"
  216. },
  217. {
  218. "name_zh": "HIS科室名称",
  219. "name_en": "HISKSMC",
  220. "data_type": "CHAR(20)",
  221. "is_primary": "否",
  222. "comment": "HIS科室名称",
  223. "nullable": "否"
  224. }
  225. ]
  226. }
  227. ]
  228. 注意:
  229. - 如果只有一个表,也要返回数组格式:[{table_info: {...}, columns: [...]}]
  230. - 如果有多个表,数组中包含多个元素:[{表1}, {表2}, {表3}]
  231. 请仅返回JSON格式结果,不要包含任何其他解释文字。
  232. """
  233. def _optimize_ddl_source_prompt(self):
  234. """返回优化后的提示词模板"""
  235. return """
  236. 请解析以下DDL建表语句,并按照指定的JSON格式返回结果:
  237. 规则说明:
  238. 1. 从DDL语句中识别所有表名,并在data对象中为每个表创建条目,表名请使用小写,可能会有多个表。
  239. 2. 对于每个表,提取所有字段信息,包括名称、数据类型和注释。
  240. - 中文表名中不要出现标点符号
  241. 3. 字段中文名称(name_zh)的确定规则:
  242. - 如有COMMENT注释,直接使用注释内容
  243. - 如无注释但字段名有明确含义,将英文名翻译为中文
  244. - 如字段名是无意义的拼音缩写,则name_zh为空字符串
  245. - 字段名中不要出现逗号,以及"主键"、"外键"、"索引"等字样
  246. 4. 所有的表的定义信息,请放在tables对象中, tables对象的key为表名,value为表的定义信息。这里可能会有多个表,请一一识别。
  247. 5. data_source对象,请放在data_source标签中,它与tables对象同级。
  248. 6. 数据库连接串处理:
  249. - 将连接串识别后并拆解为:主机名/IP地址、端口、数据库名称、用户名、密码。
  250. - 根据连接串格式识别数据库类型,数据库类型请使用小写,参考例子,如 mysql/postgresql/sqlserver/oracle/db2/sybase
  251. - data_source.name_en格式为: "{数据库名称}_{hostname或ip地址}_{端口}_{数据库用户名}",如某个元素无法识别,则跳过不添加.
  252. - data_source.name_zh留空.
  253. - 无法确定数据库类型时,type设为"unknown"
  254. - 如果从ddl中没有识别到数据库连接串,则json不返回"data_source"标签
  255. - 除了database,password,username,name_en,host,port,type,name_zh 之外,连接串的其它字段放在param属性中。
  256. 7. 参考格式如下:
  257. {
  258. "tables": {
  259. "users": { //表名
  260. "name_zh": "用户表", //表的中文名,来自于COMMENT注释或LLM翻译,如果无法确定,则name_zh为空字符串
  261. "schema": "public",
  262. "meta": [{
  263. "name_en": "id",
  264. "data_type": "integer",
  265. "name_zh": "用户ID"
  266. },
  267. {
  268. "name_en": "username",
  269. "data_type": "varchar",
  270. "name_zh": "用户名"
  271. }
  272. ]
  273. }
  274. },
  275. "data_source": [{
  276. "name_en": "mydatabase_10.52.31.104_5432_myuser", //{数据库名称}_{hostname或ip地址}_{端口}_{数据库用户名}
  277. "name_zh": "", //如果没有注释,这里留空
  278. "type": "postgresql",
  279. "host": "10.52.31.104",
  280. "port": 5432,
  281. "database": "mydatabase",
  282. "username": "myuser",
  283. "password": "mypassword",
  284. "param": "useUnicode=true&characterEncoding=utf8&serverTimezone=UTC"
  285. }]
  286. }
  287. 请仅返回JSON格式结果,不要包含任何其他解释文字。
  288. """
  289. def _optimize_connstr_parse_prompt(self):
  290. """返回优化后的连接字符串解析提示词模板"""
  291. return """
  292. 请解析以下数据库连接字符串,并按照指定的JSON格式返回结果:
  293. 规则说明:
  294. 1. 将连接串识别后并拆解为:主机名/IP地址、端口、数据库名称、用户名、密码。
  295. 2. 根据连接串格式识别数据库类型,数据库类型请使用小写,如 mysql/postgresql/sqlserver/oracle/db2/sybase
  296. 3. data_source.name_en格式为: "{数据库名称}_{hostname或ip地址}_{端口}_{数据库用户名}",如某个元素无法识别,则跳过不添加
  297. 4. data_source.name_zh留空
  298. 5. 无法确定数据库类型时,type设为"unknown"
  299. 6. 除了database,password,username,name_en,host,port,type,name_zh 之外,连接串的其它字段放在param属性中
  300. 返回格式示例:
  301. {
  302. "data_source": {
  303. "name_en": "mydatabase_10.52.31.104_5432_myuser",
  304. "name_zh": "",
  305. "type": "postgresql",
  306. "host": "10.52.31.104",
  307. "port": 5432,
  308. "database": "mydatabase",
  309. "username": "myuser",
  310. "password": "mypassword",
  311. "param": "useUnicode=true&characterEncoding=utf8&serverTimezone=UTC"
  312. }
  313. }
  314. 请仅返回JSON格式结果,不要包含任何其他解释文字。
  315. """
  316. def _optimize_connstr_valid_prompt(self):
  317. """返回优化后的连接字符串验证提示词模板"""
  318. return """
  319. 请验证以下数据库连接信息是否符合规则:
  320. 规则说明:
  321. 1. 必填字段检查:
  322. - database: 数据库名称,不能为空,符合数据库名称的命名规范。
  323. - name_en: 格式必须为 "{数据库名称}_{hostname或ip地址}_{端口}_{数据库用户名}"
  324. - host: 主机名或IP地址,不能为空
  325. - port: 端口号,必须为数字
  326. - type: 数据库类型,必须为以下之一:mysql/postgresql/sqlserver/oracle/db2/sybase
  327. - username: 用户名,不能为空,名称中间不能有空格。
  328. 2. 字段格式检查:
  329. - en_name中的各个部分必须与对应的字段值匹配
  330. - port必须是有效的端口号(1-65535)
  331. - type必须是小写的数据库类型名称
  332. - param中的参数格式必须正确(key=value格式)
  333. 3. 可选字段:
  334. - password: 密码(可选)
  335. - name: 中文名称(可选)
  336. - desc: 描述(可选)
  337. 请检查提供的连接信息是否符合以上规则,如果符合则返回"success",否则返回"failure"。
  338. 请仅返回"success"或"failure",不要包含任何其他解释文字。
  339. """
  340. def valid_db_conn_str(self, conn_str):
  341. """
  342. 验证数据库连接字符串是否符合规则
  343. 参数:
  344. conn_str: 要验证的数据库连接信息(JSON格式)
  345. 返回:
  346. "success" 或 "failure"
  347. """
  348. prompt = self._optimize_connstr_valid_prompt()
  349. payload = {
  350. "model": self.model_name,
  351. "messages": [
  352. {
  353. "role": "system",
  354. "content": "你是一个专业的数据库连接信息验证专家,擅长验证数据库连接信息的完整性和正确性。",
  355. },
  356. {
  357. "role": "user",
  358. "content": f"{prompt}\n\n{json.dumps(conn_str, ensure_ascii=False)}",
  359. },
  360. ],
  361. }
  362. try:
  363. result = self._make_llm_request(payload, "连接字符串验证")
  364. if not result:
  365. logger.error(
  366. f"连接字符串验证失败: 在{self.max_retries}次尝试后仍然失败"
  367. )
  368. return "failure"
  369. if "choices" in result and len(result["choices"]) > 0:
  370. content = result["choices"][0]["message"]["content"].strip().lower()
  371. return "success" if content == "success" else "failure"
  372. return "failure"
  373. except Exception as e:
  374. logger.error(f"LLM 验证数据库连接字符串失败: {str(e)}")
  375. return "failure"
  376. def parse_excel_content(self, file_content: bytes) -> list[dict[str, Any]]:
  377. """
  378. 解析 Excel 文件内容,提取数据表定义信息
  379. Args:
  380. file_content: Excel 文件的二进制内容
  381. Returns:
  382. 解析后的表结构列表
  383. """
  384. try:
  385. import pandas as pd
  386. # 读取 Excel 文件的所有 sheet
  387. excel_file = io.BytesIO(file_content)
  388. xl = pd.ExcelFile(excel_file)
  389. # 将所有 sheet 的内容转换为文本
  390. all_content = []
  391. for sheet_name in xl.sheet_names:
  392. df = pd.read_excel(xl, sheet_name=sheet_name)
  393. # 将 DataFrame 转换为 markdown 表格格式
  394. sheet_content = f"## Sheet: {sheet_name}\n"
  395. sheet_content += df.to_markdown(index=False)
  396. all_content.append(sheet_content)
  397. combined_content = "\n\n".join(all_content)
  398. logger.info(f"Excel 文件解析完成,共 {len(xl.sheet_names)} 个 sheet")
  399. # 使用 LLM 解析表结构
  400. return self._parse_document_content(combined_content, "Excel")
  401. except Exception as e:
  402. logger.error(f"Excel 文件解析失败: {str(e)}")
  403. raise ValueError(f"Excel 文件解析失败: {str(e)}") from e
  404. def parse_word_content(self, file_content: bytes) -> list[dict[str, Any]]:
  405. """
  406. 解析 Word 文件内容,提取数据表定义信息
  407. Args:
  408. file_content: Word 文件的二进制内容
  409. Returns:
  410. 解析后的表结构列表
  411. """
  412. try:
  413. from docx import Document
  414. # 读取 Word 文件
  415. doc = Document(io.BytesIO(file_content))
  416. # 提取所有段落文本
  417. paragraphs = [para.text for para in doc.paragraphs if para.text.strip()]
  418. # 提取所有表格
  419. tables_content = []
  420. for table_idx, table in enumerate(doc.tables):
  421. table_text = f"\n### 表格 {table_idx + 1}:\n"
  422. for row in table.rows:
  423. row_text = " | ".join(cell.text.strip() for cell in row.cells)
  424. table_text += row_text + "\n"
  425. tables_content.append(table_text)
  426. # 组合内容
  427. combined_content = "\n".join(paragraphs)
  428. if tables_content:
  429. combined_content += "\n\n## 文档中的表格:\n" + "\n".join(tables_content)
  430. logger.info(
  431. f"Word 文件解析完成,共 {len(paragraphs)} 个段落,{len(doc.tables)} 个表格"
  432. )
  433. # 使用 LLM 解析表结构
  434. return self._parse_document_content(combined_content, "Word")
  435. except Exception as e:
  436. logger.error(f"Word 文件解析失败: {str(e)}")
  437. raise ValueError(f"Word 文件解析失败: {str(e)}") from e
  438. def parse_pdf_content(self, file_content: bytes) -> list[dict[str, Any]]:
  439. """
  440. 解析 PDF 文件内容,提取数据表定义信息
  441. Args:
  442. file_content: PDF 文件的二进制内容
  443. Returns:
  444. 解析后的表结构列表
  445. """
  446. try:
  447. import pdfplumber
  448. # 读取 PDF 文件
  449. pdf = pdfplumber.open(io.BytesIO(file_content))
  450. all_content = []
  451. for page_num, page in enumerate(pdf.pages):
  452. page_text = f"## 第 {page_num + 1} 页:\n"
  453. # 提取页面文本
  454. text = page.extract_text()
  455. if text:
  456. page_text += text + "\n"
  457. # 提取页面中的表格
  458. tables = page.extract_tables()
  459. for table_idx, table in enumerate(tables):
  460. page_text += f"\n### 表格 {table_idx + 1}:\n"
  461. for row in table:
  462. row_text = " | ".join(str(cell) if cell else "" for cell in row)
  463. page_text += row_text + "\n"
  464. all_content.append(page_text)
  465. pdf.close()
  466. combined_content = "\n\n".join(all_content)
  467. logger.info(f"PDF 文件解析完成,共 {len(pdf.pages)} 页")
  468. # 使用 LLM 解析表结构
  469. return self._parse_document_content(combined_content, "PDF")
  470. except Exception as e:
  471. logger.error(f"PDF 文件解析失败: {str(e)}")
  472. raise ValueError(f"PDF 文件解析失败: {str(e)}") from e
  473. def _parse_document_content(
  474. self, content: str, file_type: str
  475. ) -> list[dict[str, Any]]:
  476. """
  477. 使用 LLM 解析文档内容,提取数据表定义信息
  478. Args:
  479. content: 文档的文本内容
  480. file_type: 文件类型(用于日志记录)
  481. Returns:
  482. 解析后的表结构列表
  483. """
  484. prompt = self._get_document_parse_prompt()
  485. payload = {
  486. "model": self.model_name,
  487. "messages": [
  488. {
  489. "role": "system",
  490. "content": "你是一个专业的数据表结构解析专家,擅长从各种文档中识别和提取数据表定义信息并转换为结构化的JSON格式。",
  491. },
  492. {"role": "user", "content": f"{prompt}\n\n{content}"},
  493. ],
  494. }
  495. try:
  496. result = self._make_llm_request(payload, f"{file_type}文档解析")
  497. if not result:
  498. raise ValueError(f"API请求失败: 在{self.max_retries}次尝试后仍然失败")
  499. if "choices" in result and len(result["choices"]) > 0:
  500. response_content = result["choices"][0]["message"]["content"]
  501. try:
  502. json_match = re.search(
  503. r"```json\s*([\s\S]*?)\s*```", response_content
  504. )
  505. if json_match:
  506. json_content = json_match.group(1)
  507. else:
  508. json_content = response_content
  509. parsed_result = json.loads(json_content)
  510. # 确保返回的是列表格式
  511. if isinstance(parsed_result, dict):
  512. parsed_result = [parsed_result]
  513. return parsed_result
  514. except json.JSONDecodeError as e:
  515. raise ValueError(f"无法解析返回的JSON: {str(e)}") from e
  516. raise ValueError("无法获取有效响应")
  517. except Exception as e:
  518. logger.error(f"{file_type}文档解析异常: {str(e)}")
  519. raise
  520. def _get_document_parse_prompt(self) -> str:
  521. """返回文档解析的提示词模板"""
  522. return """
  523. 请从以下文档内容中识别并提取所有数据表的定义信息,按照指定的JSON格式返回结果。
  524. 规则说明:
  525. 1. 仔细阅读文档内容,识别所有描述数据表结构的部分。
  526. 2. 一个文档可能包含一个或多个数据表的定义,请将所有表放在一个JSON数组中返回。
  527. 3. 表的英文名称(name_en):
  528. - 如果文档中有英文表名,使用原始大小写
  529. - 如果没有英文名,尝试根据中文名翻译或生成合适的英文名
  530. 4. 表的中文名称(name_zh):
  531. - 从文档中提取表的中文名称或描述
  532. - 如果没有明确的中文名,根据内容推断
  533. 5. 对于每个表,提取所有字段信息到columns数组中,每个字段包含:
  534. - name_zh: 字段中文名称
  535. - name_en: 字段英文名称(如果没有,根据中文名翻译)
  536. - data_type: 数据类型(如VARCHAR(255)、INTEGER、DATE等,如果文档未指定则根据字段用途推断)
  537. - is_primary: 是否主键("是"或"否")
  538. - comment: 字段说明或注释
  539. - nullable: 是否可为空("是"或"否",如果文档未指定默认为"是")
  540. 6. 返回格式(必须是JSON数组):
  541. [
  542. {
  543. "table_info": {
  544. "name_zh": "用户信息表",
  545. "name_en": "user_info"
  546. },
  547. "columns": [
  548. {
  549. "name_zh": "用户ID",
  550. "name_en": "user_id",
  551. "data_type": "INTEGER",
  552. "is_primary": "是",
  553. "comment": "用户唯一标识",
  554. "nullable": "否"
  555. },
  556. {
  557. "name_zh": "用户名",
  558. "name_en": "username",
  559. "data_type": "VARCHAR(50)",
  560. "is_primary": "否",
  561. "comment": "用户登录名",
  562. "nullable": "否"
  563. }
  564. ]
  565. }
  566. ]
  567. 注意:
  568. - 即使只识别到一个表,也必须返回数组格式:[{table_info: {...}, columns: [...]}]
  569. - 如果文档中没有找到任何数据表定义,返回空数组:[]
  570. - 请仅返回JSON格式结果,不要包含任何其他解释文字。
  571. """