resource.py 52 KB

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