resource.py 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043
  1. import json
  2. import re
  3. import logging
  4. from py2neo import Relationship
  5. import pandas as pd
  6. from app.services.neo4j_driver import neo4j_driver
  7. from app.services.package_function import create_or_get_node, relationship_exists, get_node
  8. logger = logging.getLogger("app")
  9. def get_formatted_time():
  10. """获取格式化的当前时间"""
  11. import time
  12. return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
  13. def get_node_by_id(label, id):
  14. """根据ID获取指定标签的节点"""
  15. try:
  16. with neo4j_driver.get_session() as session:
  17. cypher = f"MATCH (n:{label}) WHERE id(n) = $id RETURN n"
  18. result = session.run(cypher, id=int(id))
  19. record = result.single()
  20. return record["n"] if record else None
  21. except Exception as e:
  22. logger.error(f"根据ID获取节点失败: {str(e)}")
  23. return None
  24. def get_node_by_id_no_label(id):
  25. """根据ID获取节点,不限制标签"""
  26. try:
  27. with neo4j_driver.get_session() as session:
  28. cypher = "MATCH (n) WHERE id(n) = $id RETURN n"
  29. result = session.run(cypher, id=int(id))
  30. record = result.single()
  31. return record["n"] if record else None
  32. except Exception as e:
  33. logger.error(f"根据ID获取节点失败: {str(e)}")
  34. return None
  35. def delete_relationships(start_node, rel_type=None, end_node=None):
  36. """删除关系"""
  37. try:
  38. with neo4j_driver.get_session() as session:
  39. if rel_type and end_node:
  40. cypher = "MATCH (a)-[r:`{rel_type}`]->(b) WHERE id(a) = $start_id AND id(b) = $end_id DELETE r"
  41. cypher = cypher.replace("{rel_type}", rel_type)
  42. session.run(cypher, start_id=start_node.id, end_id=end_node.id)
  43. elif rel_type:
  44. cypher = "MATCH (a)-[r:`{rel_type}`]->() WHERE id(a) = $start_id DELETE r"
  45. cypher = cypher.replace("{rel_type}", rel_type)
  46. session.run(cypher, start_id=start_node.id)
  47. else:
  48. cypher = "MATCH (a)-[r]->() WHERE id(a) = $start_id DELETE r"
  49. session.run(cypher, start_id=start_node.id)
  50. return True
  51. except Exception as e:
  52. logger.error(f"删除关系失败: {str(e)}")
  53. return False
  54. def update_or_create_node(label, **properties):
  55. """更新或创建节点"""
  56. try:
  57. with neo4j_driver.get_session() as session:
  58. node_id = properties.pop('id', None)
  59. if node_id:
  60. # 更新现有节点
  61. set_clause = ", ".join([f"n.{k} = ${k}" for k in properties.keys()])
  62. cypher = f"MATCH (n:{label}) WHERE id(n) = $id SET {set_clause} RETURN n"
  63. result = session.run(cypher, id=int(node_id), **properties)
  64. else:
  65. # 创建新节点
  66. props_str = ", ".join([f"{k}: ${k}" for k in properties.keys()])
  67. cypher = f"CREATE (n:{label} {{{props_str}}}) RETURN n"
  68. result = session.run(cypher, **properties)
  69. record = result.single()
  70. return record["n"] if record else None
  71. except Exception as e:
  72. logger.error(f"更新或创建节点失败: {str(e)}")
  73. return None
  74. # 数据资源-元数据 关系节点创建、查询
  75. def handle_node(receiver, head_data, data_resource):
  76. """处理数据资源节点创建和关系建立"""
  77. try:
  78. # 更新属性
  79. update_attributes = {
  80. 'en_name': data_resource['en_name'],
  81. 'time': get_formatted_time(),
  82. 'type': 'structure' # 结构化文件没有type
  83. }
  84. if 'additional_info' in receiver:
  85. del receiver['additional_info']
  86. tag_list = receiver.get('tag')
  87. receiver.update(update_attributes)
  88. # 创建或获取 data_resource 节点
  89. with neo4j_driver.get_session() as session:
  90. props_str = ", ".join([f"{k}: ${k}" for k in receiver.keys()])
  91. cypher = f"""
  92. MERGE (n:data_resource {{name: $name}})
  93. ON CREATE SET n = {{{props_str}}}
  94. ON MATCH SET {", ".join([f"n.{k} = ${k}" for k in receiver.keys()])}
  95. RETURN n
  96. """
  97. result = session.run(cypher, **receiver)
  98. data_resource_node = result.single()["n"]
  99. resource_id = data_resource_node.id
  100. # 处理标签关系
  101. if tag_list:
  102. tag_node = get_node_by_id('data_label', tag_list)
  103. if tag_node:
  104. # 检查关系是否存在
  105. rel_check = """
  106. MATCH (a:data_resource)-[r:label]->(b:data_label)
  107. WHERE id(a) = $resource_id AND id(b) = $tag_id
  108. RETURN r
  109. """
  110. rel_result = session.run(rel_check, resource_id=resource_id, tag_id=tag_node.id)
  111. # 如果关系不存在则创建
  112. if not rel_result.single():
  113. rel_create = """
  114. MATCH (a:data_resource), (b:data_label)
  115. WHERE id(a) = $resource_id AND id(b) = $tag_id
  116. CREATE (a)-[r:label]->(b)
  117. RETURN r
  118. """
  119. session.run(rel_create, resource_id=resource_id, tag_id=tag_node.id)
  120. # 处理头部数据(元数据)
  121. if head_data:
  122. for item in head_data:
  123. # 创建元数据节点
  124. meta_cypher = """
  125. MERGE (m:Metadata {name: $name})
  126. ON CREATE SET m.en_name = $en_name, m.createTime = $create_time
  127. RETURN m
  128. """
  129. create_time = get_formatted_time()
  130. meta_result = session.run(
  131. meta_cypher,
  132. name=item['name'],
  133. en_name=item['en_name'],
  134. create_time=create_time
  135. )
  136. meta_node = meta_result.single()["m"]
  137. # 创建关系
  138. rel_cypher = """
  139. MATCH (a:data_resource), (m:Metadata)
  140. WHERE id(a) = $resource_id AND id(m) = $meta_id
  141. MERGE (a)-[r:contain]->(m)
  142. RETURN r
  143. """
  144. session.run(
  145. rel_cypher,
  146. resource_id=resource_id,
  147. meta_id=meta_node.id
  148. )
  149. return resource_id
  150. except Exception as e:
  151. logger.error(f"处理数据资源节点创建和关系建立失败: {str(e)}")
  152. raise
  153. def handle_id_resource(resource_id):
  154. """处理单个数据资源查询"""
  155. try:
  156. with neo4j_driver.get_session() as session:
  157. # 查询数据资源节点
  158. cypher = """
  159. MATCH (n:data_resource)
  160. WHERE id(n) = $resource_id
  161. RETURN n
  162. """
  163. result = session.run(cypher, resource_id=int(resource_id))
  164. record = result.single()
  165. if not record:
  166. return None
  167. data_resource = dict(record["n"])
  168. data_resource["id"] = record["n"].id
  169. # 查询关联的标签
  170. tag_cypher = """
  171. MATCH (n:data_resource)-[:label]->(t:data_label)
  172. WHERE id(n) = $resource_id
  173. RETURN t
  174. """
  175. tag_result = session.run(tag_cypher, resource_id=int(resource_id))
  176. tag_record = tag_result.single()
  177. if tag_record:
  178. tag = dict(tag_record["t"])
  179. tag["id"] = tag_record["t"].id
  180. data_resource["tag_info"] = tag
  181. # 查询关联的元数据
  182. meta_cypher = """
  183. MATCH (n:data_resource)-[:contain]->(m:Metadata)
  184. WHERE id(n) = $resource_id
  185. RETURN m
  186. """
  187. meta_result = session.run(meta_cypher, resource_id=int(resource_id))
  188. meta_list = []
  189. for meta_record in meta_result:
  190. meta = dict(meta_record["m"])
  191. meta["id"] = meta_record["m"].id
  192. meta_list.append(meta)
  193. data_resource["meta_list"] = meta_list
  194. return data_resource
  195. except Exception as e:
  196. logger.error(f"处理单个数据资源查询失败: {str(e)}")
  197. return None
  198. def id_resource_graph(resource_id):
  199. """获取数据资源图谱"""
  200. try:
  201. with neo4j_driver.get_session() as session:
  202. # 查询数据资源节点及其关系
  203. cypher = """
  204. MATCH (n:data_resource)-[r]-(m)
  205. WHERE id(n) = $resource_id
  206. RETURN n, r, m
  207. """
  208. result = session.run(cypher, resource_id=int(resource_id))
  209. # 收集节点和关系
  210. nodes = {}
  211. relationships = []
  212. for record in result:
  213. # 处理源节点
  214. source_node = dict(record["n"])
  215. source_node["id"] = record["n"].id
  216. nodes[source_node["id"]] = source_node
  217. # 处理目标节点
  218. target_node = dict(record["m"])
  219. target_node["id"] = record["m"].id
  220. nodes[target_node["id"]] = target_node
  221. # 处理关系
  222. rel = record["r"]
  223. relationship = {
  224. "id": rel.id,
  225. "source": record["n"].id,
  226. "target": record["m"].id,
  227. "type": rel.type
  228. }
  229. relationships.append(relationship)
  230. return {
  231. "nodes": list(nodes.values()),
  232. "relationships": relationships
  233. }
  234. except Exception as e:
  235. logger.error(f"获取数据资源图谱失败: {str(e)}")
  236. return {"nodes": [], "relationships": []}
  237. def resource_list(page, page_size, en_name_filter=None, name_filter=None,
  238. type_filter='all', category_filter=None, tag_filter=None):
  239. """获取数据资源列表"""
  240. try:
  241. with neo4j_driver.get_session() as session:
  242. # 构建查询条件
  243. match_clause = "MATCH (n:data_resource)"
  244. where_conditions = []
  245. if en_name_filter:
  246. where_conditions.append(f"n.en_name CONTAINS '{en_name_filter}'")
  247. if name_filter:
  248. where_conditions.append(f"n.name CONTAINS '{name_filter}'")
  249. if type_filter and type_filter != 'all':
  250. where_conditions.append(f"n.type = '{type_filter}'")
  251. if category_filter:
  252. where_conditions.append(f"n.category = '{category_filter}'")
  253. # 标签过滤需要额外的匹配
  254. if tag_filter:
  255. match_clause += "-[:label]->(t:data_label)"
  256. where_conditions.append(f"t.name = '{tag_filter}'")
  257. where_clause = " WHERE " + " AND ".join(where_conditions) if where_conditions else ""
  258. # 计算总数
  259. count_cypher = f"{match_clause}{where_clause} RETURN count(n) as count"
  260. count_result = session.run(count_cypher)
  261. total_count = count_result.single()["count"]
  262. # 分页查询
  263. skip = (page - 1) * page_size
  264. cypher = f"""
  265. {match_clause}{where_clause}
  266. RETURN n
  267. ORDER BY n.time DESC
  268. SKIP {skip} LIMIT {page_size}
  269. """
  270. result = session.run(cypher)
  271. # 格式化结果
  272. resources = []
  273. for record in result:
  274. node = dict(record["n"])
  275. node["id"] = record["n"].id
  276. # 查询关联的标签
  277. tag_cypher = """
  278. MATCH (n:data_resource)-[:label]->(t:data_label)
  279. WHERE id(n) = $resource_id
  280. RETURN t
  281. """
  282. tag_result = session.run(tag_cypher, resource_id=node["id"])
  283. tag_record = tag_result.single()
  284. if tag_record:
  285. tag = dict(tag_record["t"])
  286. tag["id"] = tag_record["t"].id
  287. node["tag_info"] = tag
  288. resources.append(node)
  289. return resources, total_count
  290. except Exception as e:
  291. logger.error(f"获取数据资源列表失败: {str(e)}")
  292. return [], 0
  293. def id_data_search_list(resource_id, page, page_size, en_name_filter=None,
  294. name_filter=None, category_filter=None, tag_filter=None):
  295. """获取特定数据资源关联的元数据列表"""
  296. try:
  297. with neo4j_driver.get_session() as session:
  298. # 基本匹配语句
  299. match_clause = """
  300. MATCH (n:data_resource)-[:contain]->(m:Metadata)
  301. WHERE id(n) = $resource_id
  302. """
  303. where_conditions = []
  304. if en_name_filter:
  305. where_conditions.append(f"m.en_name CONTAINS '{en_name_filter}'")
  306. if name_filter:
  307. where_conditions.append(f"m.name CONTAINS '{name_filter}'")
  308. if category_filter:
  309. where_conditions.append(f"m.category = '{category_filter}'")
  310. # 标签过滤需要额外的匹配
  311. tag_match = ""
  312. if tag_filter:
  313. tag_match = "MATCH (m)-[:HAS_TAG]->(t:Tag) WHERE t.name = $tag_filter"
  314. where_clause = " AND " + " AND ".join(where_conditions) if where_conditions else ""
  315. # 计算总数
  316. count_cypher = f"""
  317. {match_clause}{where_clause}
  318. {tag_match}
  319. RETURN count(m) as count
  320. """
  321. count_params = {"resource_id": int(resource_id)}
  322. if tag_filter:
  323. count_params["tag_filter"] = tag_filter
  324. count_result = session.run(count_cypher, **count_params)
  325. total_count = count_result.single()["count"]
  326. # 分页查询
  327. skip = (page - 1) * page_size
  328. cypher = f"""
  329. {match_clause}{where_clause}
  330. {tag_match}
  331. RETURN m
  332. ORDER BY m.name
  333. SKIP {skip} LIMIT {page_size}
  334. """
  335. result = session.run(cypher, **count_params)
  336. # 格式化结果
  337. metadata_list = []
  338. for record in result:
  339. node = dict(record["m"])
  340. node["id"] = record["m"].id
  341. metadata_list.append(node)
  342. return metadata_list, total_count
  343. except Exception as e:
  344. logger.error(f"获取数据资源关联的元数据列表失败: {str(e)}")
  345. return [], 0
  346. def resource_kinship_graph(resource_id, include_meta=True):
  347. """获取数据资源亲缘关系图谱"""
  348. try:
  349. with neo4j_driver.get_session() as session:
  350. # 基本查询
  351. cypher_parts = [
  352. "MATCH (n:data_resource) WHERE id(n) = $resource_id",
  353. "OPTIONAL MATCH (n)-[:label]->(l:data_label)",
  354. ]
  355. # 是否包含元数据
  356. if include_meta:
  357. cypher_parts.append("OPTIONAL MATCH (n)-[:contain]->(m:Metadata)")
  358. cypher_parts.append("RETURN n, l, collect(m) as metadata")
  359. cypher = "\n".join(cypher_parts)
  360. result = session.run(cypher, resource_id=int(resource_id))
  361. record = result.single()
  362. if not record:
  363. return {"nodes": [], "relationships": []}
  364. # 收集节点和关系
  365. nodes = {}
  366. relationships = []
  367. # 处理数据资源节点
  368. resource_node = dict(record["n"])
  369. resource_node["id"] = record["n"].id
  370. resource_node["labels"] = list(record["n"].labels)
  371. nodes[resource_node["id"]] = resource_node
  372. # 处理标签节点
  373. if record["l"]:
  374. label_node = dict(record["l"])
  375. label_node["id"] = record["l"].id
  376. label_node["labels"] = list(record["l"].labels)
  377. nodes[label_node["id"]] = label_node
  378. # 添加资源-标签关系
  379. relationships.append({
  380. "id": f"rel-{resource_node['id']}-label-{label_node['id']}",
  381. "source": resource_node["id"],
  382. "target": label_node["id"],
  383. "type": "label"
  384. })
  385. # 处理元数据节点
  386. if include_meta and record["metadata"]:
  387. for meta in record["metadata"]:
  388. if meta: # 检查元数据节点是否存在
  389. meta_node = dict(meta)
  390. meta_node["id"] = meta.id
  391. meta_node["labels"] = list(meta.labels)
  392. nodes[meta_node["id"]] = meta_node
  393. # 添加资源-元数据关系
  394. relationships.append({
  395. "id": f"rel-{resource_node['id']}-contain-{meta_node['id']}",
  396. "source": resource_node["id"],
  397. "target": meta_node["id"],
  398. "type": "contain"
  399. })
  400. return {
  401. "nodes": list(nodes.values()),
  402. "relationships": relationships
  403. }
  404. except Exception as e:
  405. logger.error(f"获取数据资源亲缘关系图谱失败: {str(e)}")
  406. return {"nodes": [], "relationships": []}
  407. def resource_impact_all_graph(resource_id, include_meta=True):
  408. """获取数据资源影响关系图谱"""
  409. try:
  410. with neo4j_driver.get_session() as session:
  411. # 根据meta参数决定查询深度
  412. if include_meta:
  413. cypher = """
  414. MATCH path = (n:data_resource)-[*1..3]-(m)
  415. WHERE id(n) = $resource_id
  416. RETURN path
  417. """
  418. else:
  419. cypher = """
  420. MATCH path = (n:data_resource)-[*1..2]-(m)
  421. WHERE id(n) = $resource_id
  422. AND NOT (m:Metadata)
  423. RETURN path
  424. """
  425. result = session.run(cypher, resource_id=int(resource_id))
  426. # 收集节点和关系
  427. nodes = {}
  428. relationships = {}
  429. for record in result:
  430. path = record["path"]
  431. # 处理路径中的所有节点
  432. for node in path.nodes:
  433. if node.id not in nodes:
  434. node_dict = dict(node)
  435. node_dict["id"] = node.id
  436. node_dict["labels"] = list(node.labels)
  437. nodes[node.id] = node_dict
  438. # 处理路径中的所有关系
  439. for rel in path.relationships:
  440. if rel.id not in relationships:
  441. rel_dict = {
  442. "id": rel.id,
  443. "source": rel.start_node.id,
  444. "target": rel.end_node.id,
  445. "type": rel.type
  446. }
  447. relationships[rel.id] = rel_dict
  448. return {
  449. "nodes": list(nodes.values()),
  450. "relationships": list(relationships.values())
  451. }
  452. except Exception as e:
  453. logger.error(f"获取数据资源影响关系图谱失败: {str(e)}")
  454. return {"nodes": [], "relationships": []}
  455. def clean_type(type_str):
  456. """清洗SQL类型字符串"""
  457. # 提取基本类型,不包括长度或精度信息
  458. basic_type = re.sub(r'\(.*?\)', '', type_str).strip().upper()
  459. # 移除 VARYING 这样的后缀
  460. basic_type = re.sub(r'\s+VARYING$', '', basic_type)
  461. # 标准化常见类型
  462. type_mapping = {
  463. 'INT': 'INTEGER',
  464. 'INT4': 'INTEGER',
  465. 'INT8': 'BIGINT',
  466. 'SMALLINT': 'SMALLINT',
  467. 'BIGINT': 'BIGINT',
  468. 'FLOAT4': 'FLOAT',
  469. 'FLOAT8': 'DOUBLE',
  470. 'REAL': 'FLOAT',
  471. 'DOUBLE PRECISION': 'DOUBLE',
  472. 'NUMERIC': 'DECIMAL',
  473. 'BOOL': 'BOOLEAN',
  474. 'CHARACTER': 'CHAR',
  475. 'CHAR VARYING': 'VARCHAR',
  476. 'CHARACTER VARYING': 'VARCHAR',
  477. 'TEXT': 'TEXT',
  478. 'DATE': 'DATE',
  479. 'TIME': 'TIME',
  480. 'TIMESTAMP': 'TIMESTAMP',
  481. 'TIMESTAMPTZ': 'TIMESTAMP WITH TIME ZONE',
  482. 'BYTEA': 'BINARY',
  483. 'JSON': 'JSON',
  484. 'JSONB': 'JSONB',
  485. 'UUID': 'UUID',
  486. 'SERIAL': 'SERIAL',
  487. 'SERIAL4': 'SERIAL',
  488. 'SERIAL8': 'BIGSERIAL',
  489. 'BIGSERIAL': 'BIGSERIAL'
  490. }
  491. # 尝试从映射表中获取标准化的类型
  492. return type_mapping.get(basic_type, basic_type)
  493. def clean_field_name(field_name):
  494. """清洗字段名"""
  495. return field_name.strip('`').strip('"').strip("'")
  496. def select_create_ddl(sql_content):
  497. """从SQL内容中提取创建表的DDL语句"""
  498. try:
  499. # 解析复杂的SQL文件,识别所有的CREATE TABLE语句及其关联的注释
  500. # 找到所有以CREATE TABLE开头的语句块,每个语句块包含主语句和相关的注释
  501. # 首先,分割 SQL 内容按分号
  502. statements = []
  503. current_statement = ""
  504. in_string = False
  505. string_quote = None
  506. for char in sql_content:
  507. if char in ["'", '"']:
  508. if not in_string:
  509. in_string = True
  510. string_quote = char
  511. elif char == string_quote:
  512. in_string = False
  513. string_quote = None
  514. current_statement += char
  515. elif char == ';' and not in_string:
  516. current_statement += char
  517. if current_statement.strip():
  518. statements.append(current_statement.strip())
  519. current_statement = ""
  520. else:
  521. current_statement += char
  522. if current_statement.strip():
  523. statements.append(current_statement.strip())
  524. # 找出所有的CREATE TABLE语句和关联的注释
  525. create_table_statements = []
  526. create_index = -1
  527. in_table_block = False
  528. current_table = None
  529. current_block = ""
  530. for i, stmt in enumerate(statements):
  531. if re.search(r'^\s*CREATE\s+TABLE', stmt, re.IGNORECASE):
  532. # 如果已经在处理表,先保存当前块
  533. if in_table_block and current_block:
  534. create_table_statements.append(current_block)
  535. # 开始新的表块
  536. in_table_block = True
  537. current_block = stmt
  538. # 提取表名
  539. table_match = re.search(r'CREATE\s+TABLE\s+(?:(?:"[^"]+"|\'[^\']+\'|[^"\'\s\.]+)\.)?(?:"([^"]+)"|\'([^\']+)\'|([^"\'\s\(]+))', stmt, re.IGNORECASE)
  540. if table_match:
  541. current_table = table_match.group(1) or table_match.group(2) or table_match.group(3)
  542. current_table = current_table.strip('"\'') if current_table else ""
  543. elif in_table_block and (re.search(r'COMMENT\s+ON\s+TABLE', stmt, re.IGNORECASE) or
  544. re.search(r'COMMENT\s+ON\s+COLUMN', stmt, re.IGNORECASE)):
  545. # 检查注释是否属于当前表
  546. if current_table:
  547. # 表注释处理
  548. if re.search(r'COMMENT\s+ON\s+TABLE', stmt, re.IGNORECASE):
  549. table_comment_match = re.search(r'COMMENT\s+ON\s+TABLE\s+[\'"]?(\w+)[\'"]?', stmt, re.IGNORECASE)
  550. if table_comment_match:
  551. comment_table = table_comment_match.group(1).strip('"\'')
  552. if comment_table == current_table:
  553. current_block += " " + stmt
  554. else:
  555. # 这是另一个表的注释,当前表的DDL到此结束
  556. create_table_statements.append(current_block)
  557. in_table_block = False
  558. current_block = ""
  559. current_table = None
  560. # 列注释处理
  561. elif re.search(r'COMMENT\s+ON\s+COLUMN', stmt, re.IGNORECASE):
  562. column_comment_match = re.search(r'COMMENT\s+ON\s+COLUMN\s+[\'"]?(\w+)[\'"]?\.', stmt, re.IGNORECASE)
  563. if column_comment_match:
  564. comment_table = column_comment_match.group(1).strip('"\'')
  565. if comment_table == current_table:
  566. current_block += " " + stmt
  567. else:
  568. # 这是另一个表的注释,当前表的DDL到此结束
  569. create_table_statements.append(current_block)
  570. in_table_block = False
  571. current_block = ""
  572. current_table = None
  573. elif in_table_block and re.search(r'^\s*CREATE\s+', stmt, re.IGNORECASE):
  574. # 如果遇到新的CREATE语句(不是注释),保存当前块并结束
  575. create_table_statements.append(current_block)
  576. in_table_block = False
  577. current_block = ""
  578. current_table = None
  579. # 添加最后一个块
  580. if in_table_block and current_block:
  581. create_table_statements.append(current_block)
  582. # 日志记录
  583. logger.debug(f"提取到 {len(create_table_statements)} 个DDL语句")
  584. for i, stmt in enumerate(create_table_statements):
  585. logger.debug(f"DDL语句 {i+1}: {stmt}")
  586. return create_table_statements
  587. except Exception as e:
  588. logger.error(f"提取DDL语句失败: {str(e)}")
  589. logger.error(traceback.format_exc())
  590. return []
  591. def table_sql(sql):
  592. """解析表定义SQL,支持带schema和不带schema两种格式"""
  593. try:
  594. # 支持以下格式:
  595. # 1. CREATE TABLE tablename
  596. # 2. CREATE TABLE "tablename"
  597. # 3. CREATE TABLE 'tablename'
  598. # 4. CREATE TABLE schema.tablename
  599. # 5. CREATE TABLE "schema"."tablename"
  600. # 6. CREATE TABLE 'schema'.'tablename'
  601. # 匹配表名,支持带引号和不带引号的情况
  602. table_pattern = r'CREATE\s+TABLE\s+(?:(?:"([^"]+)"|\'([^\']+)\'|([^"\'\s\.]+))\.)?(?:"([^"]+)"|\'([^\']+)\'|([^"\'\s\(]+))'
  603. table_match = re.search(table_pattern, sql, re.IGNORECASE)
  604. if not table_match:
  605. logger.error(f"无法匹配CREATE TABLE语句: {sql[:100]}...")
  606. return None
  607. # 获取表名
  608. schema = table_match.group(1) or table_match.group(2) or table_match.group(3)
  609. table_name = table_match.group(4) or table_match.group(5) or table_match.group(6)
  610. if not table_name:
  611. logger.error("无法解析表名")
  612. return None
  613. logger.debug(f"解析到表名: {table_name}")
  614. # 提取CREATE TABLE语句的主体部分(括号内的内容)
  615. body_pattern = r'CREATE\s+TABLE\s+[^(]*\((.*?)\)(?=\s*;|\s*$)'
  616. body_match = re.search(body_pattern, sql, re.DOTALL | re.IGNORECASE)
  617. if not body_match:
  618. logger.error("无法提取表主体内容")
  619. return None
  620. body_text = body_match.group(1).strip()
  621. logger.debug(f"表定义主体部分: {body_text}")
  622. # 解析字段定义
  623. fields = []
  624. # 分割字段定义,处理括号嵌套和引号
  625. field_defs = []
  626. pos = 0
  627. in_parentheses = 0
  628. in_quotes = False
  629. quote_char = None
  630. for i, char in enumerate(body_text):
  631. if char in ["'", '"', '`'] and (not in_quotes or char == quote_char):
  632. in_quotes = not in_quotes
  633. if in_quotes:
  634. quote_char = char
  635. else:
  636. quote_char = None
  637. elif char == '(' and not in_quotes:
  638. in_parentheses += 1
  639. elif char == ')' and not in_quotes:
  640. in_parentheses -= 1
  641. elif char == ',' and in_parentheses == 0 and not in_quotes:
  642. field_defs.append(body_text[pos:i].strip())
  643. pos = i + 1
  644. # 添加最后一个字段定义
  645. if pos < len(body_text):
  646. field_defs.append(body_text[pos:].strip())
  647. logger.debug(f"解析出 {len(field_defs)} 个字段定义")
  648. # 处理每个字段定义
  649. for field_def in field_defs:
  650. # 跳过约束定义
  651. if re.match(r'^\s*(?:PRIMARY|UNIQUE|FOREIGN|CHECK|CONSTRAINT)\s+', field_def, re.IGNORECASE):
  652. continue
  653. # 提取字段名和类型
  654. field_pattern = r'^\s*(?:"([^"]+)"|\'([^\']+)\'|`([^`]+)`|([a-zA-Z0-9_]+))\s+(.+?)(?:\s+DEFAULT\s+|\s+NOT\s+NULL|\s+REFERENCES|\s*$)'
  655. field_match = re.search(field_pattern, field_def, re.IGNORECASE)
  656. if field_match:
  657. # 提取字段名
  658. field_name = field_match.group(1) or field_match.group(2) or field_match.group(3) or field_match.group(4)
  659. # 提取类型
  660. field_type = field_match.group(5).strip()
  661. # 处理类型中可能的括号
  662. type_base = re.split(r'\s+', field_type)[0]
  663. clean_type_value = clean_type(type_base)
  664. fields.append((field_name, clean_type_value))
  665. logger.debug(f"解析到字段: {field_name}, 类型: {clean_type_value}")
  666. else:
  667. logger.warning(f"无法解析字段定义: {field_def}")
  668. # 提取表注释
  669. table_comment = ""
  670. table_comment_pattern = r"COMMENT\s+ON\s+TABLE\s+(?:['\"]?(\w+)['\"]?)\s+IS\s+'([^']+)'"
  671. table_comment_match = re.search(table_comment_pattern, sql, re.IGNORECASE)
  672. if table_comment_match:
  673. comment_table = table_comment_match.group(1)
  674. if comment_table.strip("'\"") == table_name.strip("'\""):
  675. table_comment = table_comment_match.group(2)
  676. logger.debug(f"找到表注释: {table_comment}")
  677. # 提取列注释
  678. comments = {}
  679. column_comment_pattern = r"COMMENT\s+ON\s+COLUMN\s+['\"]?(\w+)['\"]?\.['\"]?(\w+)['\"]?\s+IS\s+'([^']+)'"
  680. for match in re.finditer(column_comment_pattern, sql, re.IGNORECASE):
  681. comment_table = match.group(1)
  682. column_name = match.group(2)
  683. comment = match.group(3)
  684. # 检查表名是否匹配
  685. if comment_table.strip("'\"") == table_name.strip("'\""):
  686. comments[column_name] = comment
  687. logger.debug(f"找到列注释: {column_name} - {comment}")
  688. else:
  689. logger.debug(f"忽略列注释,表名不匹配: {comment_table} vs {table_name}")
  690. # 检查字段和注释匹配情况
  691. logger.debug("========字段和注释匹配情况========")
  692. field_names = [f[0] for f in fields]
  693. logger.debug(f"字段列表 ({len(field_names)}): {field_names}")
  694. logger.debug(f"注释字段 ({len(comments)}): {list(comments.keys())}")
  695. # 构建返回结果
  696. meta_list = []
  697. for field_name, field_type in fields:
  698. chinese_name = comments.get(field_name, "")
  699. meta_list.append({
  700. "en_name": field_name,
  701. "data_type": field_type,
  702. "name": chinese_name if chinese_name else field_name
  703. })
  704. # 检查表是否存在
  705. try:
  706. status = status_query([table_name])
  707. except Exception as e:
  708. logger.error(f"检查表存在状态失败: {str(e)}")
  709. status = [False]
  710. # 构建返回结果
  711. result = {
  712. table_name: {
  713. "exist": status[0] if status else False,
  714. "meta": meta_list
  715. }
  716. }
  717. logger.debug(f"解析结果: {json.dumps(result, ensure_ascii=False)}")
  718. return result
  719. except Exception as e:
  720. logger.error(f"解析表定义SQL失败: {str(e)}")
  721. logger.error(f"异常详情: {e}")
  722. import traceback
  723. logger.error(traceback.format_exc())
  724. return None
  725. # 判断英文表名是否在图谱中存在
  726. def status_query(key_list):
  727. query = """
  728. unwind $Key_list as name
  729. OPTIONAL MATCH (n:data_model {en_name: name})
  730. OPTIONAL MATCH (n:data_resource {en_name: name})
  731. OPTIONAL MATCH (n:data_metric {en_name: name})
  732. WITH name, CASE
  733. WHEN n IS NOT NULL THEN True
  734. ELSE False
  735. END AS exist
  736. return collect(exist)AS exist
  737. """
  738. with neo4j_driver.get_session() as session:
  739. result = session.run(query, Key_list=key_list)
  740. data = result.value() # 获取单个值
  741. return data
  742. def select_sql(sql_query):
  743. """解析SELECT查询语句"""
  744. try:
  745. # 提取SELECT子句
  746. select_pattern = r'SELECT\s+(.*?)\s+FROM'
  747. select_match = re.search(select_pattern, sql_query, re.IGNORECASE | re.DOTALL)
  748. if not select_match:
  749. return None
  750. select_clause = select_match.group(1)
  751. # 分割字段
  752. fields = []
  753. # 处理字段列表,避免在函数调用中的逗号导致错误分割
  754. in_parenthesis = 0
  755. current_field = ""
  756. for char in select_clause:
  757. if char == '(':
  758. in_parenthesis += 1
  759. current_field += char
  760. elif char == ')':
  761. in_parenthesis -= 1
  762. current_field += char
  763. elif char == ',' and in_parenthesis == 0:
  764. fields.append(current_field.strip())
  765. current_field = ""
  766. else:
  767. current_field += char
  768. if current_field.strip():
  769. fields.append(current_field.strip())
  770. # 解析每个字段
  771. parsed_fields = []
  772. for field in fields:
  773. # 检查是否有字段别名
  774. alias_pattern = r'(.*?)\s+[aA][sS]\s+(?:`([^`]+)`|"([^"]+)"|\'([^\']+)\'|([a-zA-Z0-9_]+))$'
  775. alias_match = re.search(alias_pattern, field)
  776. if alias_match:
  777. field_expr = alias_match.group(1).strip()
  778. field_alias = next((g for g in alias_match.groups()[1:] if g is not None), "")
  779. parsed_fields.append({
  780. "expression": field_expr,
  781. "alias": field_alias
  782. })
  783. else:
  784. # 没有别名的情况
  785. parsed_fields.append({
  786. "expression": field.strip(),
  787. "alias": None
  788. })
  789. # 提取FROM子句和表名
  790. from_pattern = r'FROM\s+(.*?)(?:\s+WHERE|\s+GROUP|\s+HAVING|\s+ORDER|\s+LIMIT|$)'
  791. from_match = re.search(from_pattern, sql_query, re.IGNORECASE | re.DOTALL)
  792. tables = []
  793. if from_match:
  794. from_clause = from_match.group(1).strip()
  795. # 分析FROM子句中的表
  796. table_pattern = r'(?:`([^`]+)`|"([^"]+)"|\'([^\']+)\'|([a-zA-Z0-9_]+))(?:\s+(?:AS\s+)?(?:`([^`]+)`|"([^"]+)"|\'([^\']+)\'|([a-zA-Z0-9_]+))?'
  797. for match in re.finditer(table_pattern, from_clause):
  798. table_name = match.group(1) or match.group(2) or match.group(3) or match.group(4)
  799. if table_name:
  800. tables.append(table_name)
  801. return tables
  802. except Exception as e:
  803. logger.error(f"解析SELECT查询语句失败: {str(e)}")
  804. logger.error(traceback.format_exc())
  805. return None
  806. def model_resource_list(page, page_size, name_filter=None):
  807. """获取模型资源列表"""
  808. try:
  809. with neo4j_driver.get_session() as session:
  810. # 构建查询条件
  811. match_clause = "MATCH (n:model_resource)"
  812. where_clause = ""
  813. if name_filter:
  814. where_clause = f" WHERE n.name CONTAINS '{name_filter}'"
  815. # 计算总数
  816. count_cypher = f"{match_clause}{where_clause} RETURN count(n) as count"
  817. count_result = session.run(count_cypher)
  818. total_count = count_result.single()["count"]
  819. # 分页查询
  820. skip = (page - 1) * page_size
  821. cypher = f"""
  822. {match_clause}{where_clause}
  823. RETURN n
  824. ORDER BY n.createTime DESC
  825. SKIP {skip} LIMIT {page_size}
  826. """
  827. result = session.run(cypher)
  828. # 格式化结果
  829. resources = []
  830. for record in result:
  831. node = dict(record["n"])
  832. node["id"] = record["n"].id
  833. resources.append(node)
  834. return resources, total_count
  835. except Exception as e:
  836. logger.error(f"获取模型资源列表失败: {str(e)}")
  837. return [], 0
  838. def data_resource_edit(data):
  839. """编辑数据资源"""
  840. try:
  841. resource_id = data.get("id")
  842. if not resource_id:
  843. raise ValueError("缺少资源ID")
  844. with neo4j_driver.get_session() as session:
  845. # 更新节点属性
  846. update_fields = {}
  847. for key, value in data.items():
  848. if key != "id" and key != "tag":
  849. update_fields[key] = value
  850. # 添加更新时间
  851. update_fields["updateTime"] = get_formatted_time()
  852. # 构建更新语句
  853. set_clause = ", ".join([f"n.{k} = ${k}" for k in update_fields.keys()])
  854. cypher = f"""
  855. MATCH (n:data_resource)
  856. WHERE id(n) = $resource_id
  857. SET {set_clause}
  858. RETURN n
  859. """
  860. result = session.run(cypher, resource_id=int(resource_id), **update_fields)
  861. updated_node = result.single()
  862. if not updated_node:
  863. raise ValueError("资源不存在")
  864. # 处理标签关系
  865. tag_id = data.get("tag")
  866. if tag_id:
  867. # 删除旧的标签关系
  868. delete_rel_cypher = """
  869. MATCH (n:data_resource)-[r:label]->()
  870. WHERE id(n) = $resource_id
  871. DELETE r
  872. """
  873. session.run(delete_rel_cypher, resource_id=int(resource_id))
  874. # 创建新的标签关系
  875. create_rel_cypher = """
  876. MATCH (n:data_resource), (t:data_label)
  877. WHERE id(n) = $resource_id AND id(t) = $tag_id
  878. CREATE (n)-[r:label]->(t)
  879. RETURN r
  880. """
  881. session.run(create_rel_cypher, resource_id=int(resource_id), tag_id=int(tag_id))
  882. # 返回更新后的节点
  883. return dict(updated_node["n"])
  884. except Exception as e:
  885. logger.error(f"编辑数据资源失败: {str(e)}")
  886. raise