yangdx
commited on
Commit
·
731e6b1
1
Parent(s):
d60bf1c
Fix linting
Browse files- lightrag/kg/postgres_impl.py +16 -9
- tests/test_graph_storage.py +130 -70
lightrag/kg/postgres_impl.py
CHANGED
@@ -1037,7 +1037,10 @@ class PGGraphStorage(BaseGraphStorage):
|
|
1037 |
await self.upsert_node(node1_id, node1_data)
|
1038 |
await self.delete_node(node1_id)
|
1039 |
|
1040 |
-
query =
|
|
|
|
|
|
|
1041 |
|
1042 |
await self.db.execute(
|
1043 |
query,
|
@@ -1563,15 +1566,15 @@ class PGGraphStorage(BaseGraphStorage):
|
|
1563 |
|
1564 |
out_degrees = {}
|
1565 |
in_degrees = {}
|
1566 |
-
|
1567 |
for result in outgoing_results:
|
1568 |
if result["node_id"] is not None:
|
1569 |
out_degrees[result["node_id"]] = int(result["out_degree"])
|
1570 |
-
|
1571 |
for result in incoming_results:
|
1572 |
if result["node_id"] is not None:
|
1573 |
in_degrees[result["node_id"]] = int(result["in_degree"])
|
1574 |
-
|
1575 |
degrees_dict = {}
|
1576 |
for node_id in node_ids:
|
1577 |
out_degree = out_degrees.get(node_id, 0)
|
@@ -1659,11 +1662,15 @@ class PGGraphStorage(BaseGraphStorage):
|
|
1659 |
|
1660 |
for result in forward_results:
|
1661 |
if result["source"] and result["target"] and result["edge_properties"]:
|
1662 |
-
edges_dict[(result["source"], result["target"])] = result[
|
1663 |
-
|
|
|
|
|
1664 |
for result in backward_results:
|
1665 |
if result["source"] and result["target"] and result["edge_properties"]:
|
1666 |
-
edges_dict[(result["source"], result["target"])] = result[
|
|
|
|
|
1667 |
|
1668 |
return edges_dict
|
1669 |
|
@@ -1711,13 +1718,13 @@ class PGGraphStorage(BaseGraphStorage):
|
|
1711 |
incoming_results = await self._query(incoming_query)
|
1712 |
|
1713 |
nodes_edges_dict = {node_id: [] for node_id in node_ids}
|
1714 |
-
|
1715 |
for result in outgoing_results:
|
1716 |
if result["node_id"] and result["connected_id"]:
|
1717 |
nodes_edges_dict[result["node_id"]].append(
|
1718 |
(result["node_id"], result["connected_id"])
|
1719 |
)
|
1720 |
-
|
1721 |
for result in incoming_results:
|
1722 |
if result["node_id"] and result["connected_id"]:
|
1723 |
nodes_edges_dict[result["node_id"]].append(
|
|
|
1037 |
await self.upsert_node(node1_id, node1_data)
|
1038 |
await self.delete_node(node1_id)
|
1039 |
|
1040 |
+
query = (
|
1041 |
+
"""CREATE INDEX entity_id_gin_idxSELECT ON %s."base" USING gin (properties);"""
|
1042 |
+
% (self.graph_name)
|
1043 |
+
)
|
1044 |
|
1045 |
await self.db.execute(
|
1046 |
query,
|
|
|
1566 |
|
1567 |
out_degrees = {}
|
1568 |
in_degrees = {}
|
1569 |
+
|
1570 |
for result in outgoing_results:
|
1571 |
if result["node_id"] is not None:
|
1572 |
out_degrees[result["node_id"]] = int(result["out_degree"])
|
1573 |
+
|
1574 |
for result in incoming_results:
|
1575 |
if result["node_id"] is not None:
|
1576 |
in_degrees[result["node_id"]] = int(result["in_degree"])
|
1577 |
+
|
1578 |
degrees_dict = {}
|
1579 |
for node_id in node_ids:
|
1580 |
out_degree = out_degrees.get(node_id, 0)
|
|
|
1662 |
|
1663 |
for result in forward_results:
|
1664 |
if result["source"] and result["target"] and result["edge_properties"]:
|
1665 |
+
edges_dict[(result["source"], result["target"])] = result[
|
1666 |
+
"edge_properties"
|
1667 |
+
]
|
1668 |
+
|
1669 |
for result in backward_results:
|
1670 |
if result["source"] and result["target"] and result["edge_properties"]:
|
1671 |
+
edges_dict[(result["source"], result["target"])] = result[
|
1672 |
+
"edge_properties"
|
1673 |
+
]
|
1674 |
|
1675 |
return edges_dict
|
1676 |
|
|
|
1718 |
incoming_results = await self._query(incoming_query)
|
1719 |
|
1720 |
nodes_edges_dict = {node_id: [] for node_id in node_ids}
|
1721 |
+
|
1722 |
for result in outgoing_results:
|
1723 |
if result["node_id"] and result["connected_id"]:
|
1724 |
nodes_edges_dict[result["node_id"]].append(
|
1725 |
(result["node_id"], result["connected_id"])
|
1726 |
)
|
1727 |
+
|
1728 |
for result in incoming_results:
|
1729 |
if result["node_id"] and result["connected_id"]:
|
1730 |
nodes_edges_dict[result["node_id"]].append(
|
tests/test_graph_storage.py
CHANGED
@@ -209,7 +209,7 @@ async def test_graph_basic(storage):
|
|
209 |
else:
|
210 |
print(f"读取边属性失败: {node1_id} -> {node2_id}")
|
211 |
assert False, f"未能读取边属性: {node1_id} -> {node2_id}"
|
212 |
-
|
213 |
# 5.1 验证无向图特性 - 读取反向边属性
|
214 |
print(f"读取反向边属性: {node2_id} -> {node1_id}")
|
215 |
reverse_edge_props = await storage.get_edge(node2_id, node1_id)
|
@@ -219,11 +219,15 @@ async def test_graph_basic(storage):
|
|
219 |
print(f"反向边描述: {reverse_edge_props.get('description', '无描述')}")
|
220 |
print(f"反向边权重: {reverse_edge_props.get('weight', '无权重')}")
|
221 |
# 验证正向和反向边属性是否相同
|
222 |
-
assert
|
|
|
|
|
223 |
print("无向图特性验证成功:正向和反向边属性一致")
|
224 |
else:
|
225 |
print(f"读取反向边属性失败: {node2_id} -> {node1_id}")
|
226 |
-
assert
|
|
|
|
|
227 |
|
228 |
print("基本测试完成,数据已保留在数据库中")
|
229 |
return True
|
@@ -308,7 +312,7 @@ async def test_graph_advanced(storage):
|
|
308 |
node1_degree = await storage.node_degree(node1_id)
|
309 |
print(f"节点 {node1_id} 的度数: {node1_degree}")
|
310 |
assert node1_degree == 1, f"节点 {node1_id} 的度数应为1,实际为 {node1_degree}"
|
311 |
-
|
312 |
# 2.1 测试所有节点的度数
|
313 |
print("== 测试所有节点的度数")
|
314 |
node2_degree = await storage.node_degree(node2_id)
|
@@ -325,12 +329,14 @@ async def test_graph_advanced(storage):
|
|
325 |
assert (
|
326 |
edge_degree == 3
|
327 |
), f"边 {node1_id} -> {node2_id} 的度数应为3,实际为 {edge_degree}"
|
328 |
-
|
329 |
# 3.1 测试反向边的度数 - 验证无向图特性
|
330 |
print(f"== 测试反向边的度数: {node2_id} -> {node1_id}")
|
331 |
reverse_edge_degree = await storage.edge_degree(node2_id, node1_id)
|
332 |
print(f"反向边 {node2_id} -> {node1_id} 的度数: {reverse_edge_degree}")
|
333 |
-
assert
|
|
|
|
|
334 |
print("无向图特性验证成功:正向边和反向边的度数一致")
|
335 |
|
336 |
# 4. 测试 get_node_edges - 获取节点的所有边
|
@@ -340,7 +346,7 @@ async def test_graph_advanced(storage):
|
|
340 |
assert (
|
341 |
len(node2_edges) == 2
|
342 |
), f"节点 {node2_id} 应有2条边,实际有 {len(node2_edges)}"
|
343 |
-
|
344 |
# 4.1 验证节点边的无向图特性
|
345 |
print("== 验证节点边的无向图特性")
|
346 |
# 检查是否包含与node1和node3的连接关系(无论方向)
|
@@ -348,14 +354,22 @@ async def test_graph_advanced(storage):
|
|
348 |
has_connection_with_node3 = False
|
349 |
for edge in node2_edges:
|
350 |
# 检查是否有与node1的连接(无论方向)
|
351 |
-
if (edge[0] == node1_id and edge[1] == node2_id) or (
|
|
|
|
|
352 |
has_connection_with_node1 = True
|
353 |
# 检查是否有与node3的连接(无论方向)
|
354 |
-
if (edge[0] == node2_id and edge[1] == node3_id) or (
|
|
|
|
|
355 |
has_connection_with_node3 = True
|
356 |
-
|
357 |
-
assert
|
358 |
-
|
|
|
|
|
|
|
|
|
359 |
print(f"无向图特性验证成功:节点 {node2_id} 的边列表包含所有相关的边")
|
360 |
|
361 |
# 5. 测试 get_all_labels - 获取所有标签
|
@@ -393,12 +407,14 @@ async def test_graph_advanced(storage):
|
|
393 |
edge_props = await storage.get_edge(node2_id, node3_id)
|
394 |
print(f"删除后查询边属性 {node2_id} -> {node3_id}: {edge_props}")
|
395 |
assert edge_props is None, f"�� {node2_id} -> {node3_id} 应已被删除"
|
396 |
-
|
397 |
# 8.1 验证删除边的无向图特性
|
398 |
print(f"== 验证删除边的无向图特性: {node3_id} -> {node2_id}")
|
399 |
reverse_edge_props = await storage.get_edge(node3_id, node2_id)
|
400 |
print(f"删除后查询反向边属性 {node3_id} -> {node2_id}: {reverse_edge_props}")
|
401 |
-
assert
|
|
|
|
|
402 |
print("无向图特性验证成功:删除一个方向的边后,反向边也被删除")
|
403 |
|
404 |
# 9. 测试 remove_nodes - 批量删除节点
|
@@ -657,20 +673,27 @@ async def test_graph_batch_operations(storage):
|
|
657 |
edges_dict[(node3_id, node4_id)]["relationship"]
|
658 |
== edge5_data["relationship"]
|
659 |
), f"边 {node3_id} -> {node4_id} 关系不匹配"
|
660 |
-
|
661 |
# 5.1 测试反向边的批量获取 - 验证无向图特性
|
662 |
print("== 测试反向边的批量获取")
|
663 |
# 创建反向边的字典列表
|
664 |
reverse_edge_dicts = [{"src": tgt, "tgt": src} for src, tgt in edges]
|
665 |
reverse_edges_dict = await storage.get_edges_batch(reverse_edge_dicts)
|
666 |
print(f"批量获取反向边属性结果: {reverse_edges_dict.keys()}")
|
667 |
-
assert
|
668 |
-
|
|
|
|
|
669 |
# 验证正向和反向边的属性是否一致
|
670 |
for (src, tgt), props in edges_dict.items():
|
671 |
-
assert (
|
672 |
-
|
673 |
-
|
|
|
|
|
|
|
|
|
|
|
674 |
print("无向图特性验证成功:批量获取的正向和反向边属性一致")
|
675 |
|
676 |
# 6. 测试 get_nodes_edges_batch - 批量获取多个节点的所有边
|
@@ -688,40 +711,50 @@ async def test_graph_batch_operations(storage):
|
|
688 |
assert (
|
689 |
len(nodes_edges[node3_id]) == 3
|
690 |
), f"{node3_id} 应有3条边,实际有 {len(nodes_edges[node3_id])} 条"
|
691 |
-
|
692 |
# 6.1 验证批量获取节点边的无向图特性
|
693 |
print("== 验证批量获取节点边的无向图特性")
|
694 |
-
|
695 |
# 检查节点1的边是否包含所有相关的边(无论方向)
|
696 |
-
node1_outgoing_edges = [
|
697 |
-
|
|
|
|
|
|
|
|
|
698 |
print(f"节点 {node1_id} 的出边: {node1_outgoing_edges}")
|
699 |
print(f"节点 {node1_id} 的入边: {node1_incoming_edges}")
|
700 |
-
|
701 |
# 检查是否包含到机器学习、自然语言处理和计算机视觉的边
|
702 |
has_edge_to_node2 = any(tgt == node2_id for _, tgt in node1_outgoing_edges)
|
703 |
has_edge_to_node4 = any(tgt == node4_id for _, tgt in node1_outgoing_edges)
|
704 |
has_edge_to_node5 = any(tgt == node5_id for _, tgt in node1_outgoing_edges)
|
705 |
-
|
706 |
assert has_edge_to_node2, f"节点 {node1_id} 的边列表中应包含到 {node2_id} 的边"
|
707 |
assert has_edge_to_node4, f"节点 {node1_id} 的边列表中应包含到 {node4_id} 的边"
|
708 |
assert has_edge_to_node5, f"节点 {node1_id} 的边列表中应包含到 {node5_id} 的边"
|
709 |
-
|
710 |
# 检查节点3的边是否包含所有相关的边(无论方向)
|
711 |
-
node3_outgoing_edges = [
|
712 |
-
|
|
|
|
|
|
|
|
|
713 |
print(f"节点 {node3_id} 的出边: {node3_outgoing_edges}")
|
714 |
print(f"节点 {node3_id} 的入边: {node3_incoming_edges}")
|
715 |
-
|
716 |
# 检查是否包含从机器学习来的边,以及到自然语言处理和计算机视觉的��
|
717 |
has_edge_from_node2 = any(src == node2_id for src, _ in node3_incoming_edges)
|
718 |
has_edge_to_node4 = any(tgt == node4_id for _, tgt in node3_outgoing_edges)
|
719 |
has_edge_to_node5 = any(tgt == node5_id for _, tgt in node3_outgoing_edges)
|
720 |
-
|
721 |
-
assert
|
|
|
|
|
722 |
assert has_edge_to_node4, f"节点 {node3_id} 的边列表中应包含到 {node4_id} 的边"
|
723 |
assert has_edge_to_node5, f"节点 {node3_id} 的边列表中应包含到 {node5_id} 的边"
|
724 |
-
|
725 |
print("无向图特性验证成功:批量获取的节点边包含所有相关的边(无论方向)")
|
726 |
|
727 |
# 7. 清理数据
|
@@ -789,7 +822,7 @@ async def test_graph_undirected_property(storage):
|
|
789 |
|
790 |
# 2. 测试插入边后的无向图特性
|
791 |
print("\n== 测试插入边后的无向图特性")
|
792 |
-
|
793 |
# 插入边1: 计算机科学 -> 数据结构
|
794 |
edge1_data = {
|
795 |
"relationship": "包含",
|
@@ -798,24 +831,26 @@ async def test_graph_undirected_property(storage):
|
|
798 |
}
|
799 |
print(f"插入边1: {node1_id} -> {node2_id}")
|
800 |
await storage.upsert_edge(node1_id, node2_id, edge1_data)
|
801 |
-
|
802 |
# 验证正向查询
|
803 |
forward_edge = await storage.get_edge(node1_id, node2_id)
|
804 |
print(f"正向边属性: {forward_edge}")
|
805 |
assert forward_edge is not None, f"未能读取正向边属性: {node1_id} -> {node2_id}"
|
806 |
-
|
807 |
# 验证反向查询
|
808 |
reverse_edge = await storage.get_edge(node2_id, node1_id)
|
809 |
print(f"反向边属性: {reverse_edge}")
|
810 |
assert reverse_edge is not None, f"未能读取反向边属性: {node2_id} -> {node1_id}"
|
811 |
-
|
812 |
# 验证正向和反向边属性是否一致
|
813 |
-
assert
|
|
|
|
|
814 |
print("无向图特性验证成功:正向和反向边属性一致")
|
815 |
-
|
816 |
# 3. 测试边的度数的无向图特性
|
817 |
print("\n== 测试边的度数的无向图特性")
|
818 |
-
|
819 |
# 插入边2: 计算机科学 -> 算法
|
820 |
edge2_data = {
|
821 |
"relationship": "包含",
|
@@ -824,77 +859,102 @@ async def test_graph_undirected_property(storage):
|
|
824 |
}
|
825 |
print(f"插入边2: {node1_id} -> {node3_id}")
|
826 |
await storage.upsert_edge(node1_id, node3_id, edge2_data)
|
827 |
-
|
828 |
# 验证正向和反向边的度数
|
829 |
forward_degree = await storage.edge_degree(node1_id, node2_id)
|
830 |
reverse_degree = await storage.edge_degree(node2_id, node1_id)
|
831 |
print(f"正向边 {node1_id} -> {node2_id} 的度数: {forward_degree}")
|
832 |
print(f"反向边 {node2_id} -> {node1_id} 的度数: {reverse_degree}")
|
833 |
-
assert
|
|
|
|
|
834 |
print("无向图特性验证成功:正向和反向边的度数一致")
|
835 |
-
|
836 |
# 4. 测试删除边的无向图特性
|
837 |
print("\n== 测试删除边的无向图特性")
|
838 |
-
|
839 |
# 删除正向边
|
840 |
print(f"删除边: {node1_id} -> {node2_id}")
|
841 |
await storage.remove_edges([(node1_id, node2_id)])
|
842 |
-
|
843 |
# 验证正向边是否被删除
|
844 |
forward_edge = await storage.get_edge(node1_id, node2_id)
|
845 |
print(f"删除后查询正向边属性 {node1_id} -> {node2_id}: {forward_edge}")
|
846 |
assert forward_edge is None, f"边 {node1_id} -> {node2_id} 应已被删除"
|
847 |
-
|
848 |
# 验证反向边是否也被删除
|
849 |
reverse_edge = await storage.get_edge(node2_id, node1_id)
|
850 |
print(f"删除后查询反向边属性 {node2_id} -> {node1_id}: {reverse_edge}")
|
851 |
-
assert
|
|
|
|
|
852 |
print("无向图特性验证成功:删除一个方向的边后,反向边也被删除")
|
853 |
-
|
854 |
# 5. 测试批量操作中的无向图特性
|
855 |
print("\n== 测试批量操作中的无向图特性")
|
856 |
-
|
857 |
# 重新插入边
|
858 |
await storage.upsert_edge(node1_id, node2_id, edge1_data)
|
859 |
-
|
860 |
# 批量获取边属性
|
861 |
-
edge_dicts = [
|
862 |
-
|
863 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
864 |
edges_dict = await storage.get_edges_batch(edge_dicts)
|
865 |
reverse_edges_dict = await storage.get_edges_batch(reverse_edge_dicts)
|
866 |
-
|
867 |
print(f"批量获取正向边属性结果: {edges_dict.keys()}")
|
868 |
print(f"批量获取反向边属性结果: {reverse_edges_dict.keys()}")
|
869 |
-
|
870 |
# 验证正向和反向边的属性是否一致
|
871 |
for (src, tgt), props in edges_dict.items():
|
872 |
-
assert (
|
873 |
-
|
874 |
-
|
|
|
|
|
|
|
|
|
|
|
875 |
print("无向图特性验证成功:批量获取的正向和反向边属性一致")
|
876 |
-
|
877 |
# 6. 测试批量获取节点边的无向图特性
|
878 |
print("\n== 测试批量获取节点边的无向图特性")
|
879 |
-
|
880 |
nodes_edges = await storage.get_nodes_edges_batch([node1_id, node2_id])
|
881 |
print(f"批量获取节点边结果: {nodes_edges.keys()}")
|
882 |
-
|
883 |
# 检查节点1的边是否包含所有相关的边(无论方向)
|
884 |
node1_edges = nodes_edges[node1_id]
|
885 |
node2_edges = nodes_edges[node2_id]
|
886 |
-
|
887 |
# 检查节点1是否有到节点2和节点3的边
|
888 |
-
has_edge_to_node2 = any(
|
889 |
-
|
890 |
-
|
|
|
|
|
|
|
|
|
891 |
assert has_edge_to_node2, f"节点 {node1_id} 的边列表中应包含到 {node2_id} 的边"
|
892 |
assert has_edge_to_node3, f"节点 {node1_id} 的边列表中应包含到 {node3_id} 的边"
|
893 |
-
|
894 |
# 检查节点2是否有到节点1的边
|
895 |
-
has_edge_to_node1 = any(
|
896 |
-
|
897 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
898 |
print("无向图特性验证成功:批量获取的节点边包含所有相关的边(无论方向)")
|
899 |
|
900 |
# 7. 清理数据
|
@@ -972,7 +1032,7 @@ async def main():
|
|
972 |
if advanced_result:
|
973 |
ASCIIColors.cyan("\n=== 开始批量操作测试 ===")
|
974 |
batch_result = await test_graph_batch_operations(storage)
|
975 |
-
|
976 |
if batch_result:
|
977 |
ASCIIColors.cyan("\n=== 开始无向图特性测试 ===")
|
978 |
await test_graph_undirected_property(storage)
|
|
|
209 |
else:
|
210 |
print(f"读取边属性失败: {node1_id} -> {node2_id}")
|
211 |
assert False, f"未能读取边属性: {node1_id} -> {node2_id}"
|
212 |
+
|
213 |
# 5.1 验证无向图特性 - 读取反向边属性
|
214 |
print(f"读取反向边属性: {node2_id} -> {node1_id}")
|
215 |
reverse_edge_props = await storage.get_edge(node2_id, node1_id)
|
|
|
219 |
print(f"反向边描述: {reverse_edge_props.get('description', '无描述')}")
|
220 |
print(f"反向边权重: {reverse_edge_props.get('weight', '无权重')}")
|
221 |
# 验证正向和反向边属性是否相同
|
222 |
+
assert (
|
223 |
+
edge_props == reverse_edge_props
|
224 |
+
), "正向和反向边属性不一致,无向图特性验证失败"
|
225 |
print("无向图特性验证成功:正向和反向边属性一致")
|
226 |
else:
|
227 |
print(f"读取反向边属性失败: {node2_id} -> {node1_id}")
|
228 |
+
assert (
|
229 |
+
False
|
230 |
+
), f"未能读取反向边属性: {node2_id} -> {node1_id},无向图特性验证失败"
|
231 |
|
232 |
print("基本测试完成,数据已保留在数据库中")
|
233 |
return True
|
|
|
312 |
node1_degree = await storage.node_degree(node1_id)
|
313 |
print(f"节点 {node1_id} 的度数: {node1_degree}")
|
314 |
assert node1_degree == 1, f"节点 {node1_id} 的度数应为1,实际为 {node1_degree}"
|
315 |
+
|
316 |
# 2.1 测试所有节点的度数
|
317 |
print("== 测试所有节点的度数")
|
318 |
node2_degree = await storage.node_degree(node2_id)
|
|
|
329 |
assert (
|
330 |
edge_degree == 3
|
331 |
), f"边 {node1_id} -> {node2_id} 的度数应为3,实际为 {edge_degree}"
|
332 |
+
|
333 |
# 3.1 测试反向边的度数 - 验证无向图特性
|
334 |
print(f"== 测试反向边的度数: {node2_id} -> {node1_id}")
|
335 |
reverse_edge_degree = await storage.edge_degree(node2_id, node1_id)
|
336 |
print(f"反向边 {node2_id} -> {node1_id} 的度数: {reverse_edge_degree}")
|
337 |
+
assert (
|
338 |
+
edge_degree == reverse_edge_degree
|
339 |
+
), "正向边和反向边的度数不一致,无向图特性验证失败"
|
340 |
print("无向图特性验证成功:正向边和反向边的度数一致")
|
341 |
|
342 |
# 4. 测试 get_node_edges - 获取节点的所有边
|
|
|
346 |
assert (
|
347 |
len(node2_edges) == 2
|
348 |
), f"节点 {node2_id} 应有2条边,实际有 {len(node2_edges)}"
|
349 |
+
|
350 |
# 4.1 验证节点边的无向图特性
|
351 |
print("== 验证节点边的无向图特性")
|
352 |
# 检查是否包含与node1和node3的连接关系(无论方向)
|
|
|
354 |
has_connection_with_node3 = False
|
355 |
for edge in node2_edges:
|
356 |
# 检查是否有与node1的连接(无论方向)
|
357 |
+
if (edge[0] == node1_id and edge[1] == node2_id) or (
|
358 |
+
edge[0] == node2_id and edge[1] == node1_id
|
359 |
+
):
|
360 |
has_connection_with_node1 = True
|
361 |
# 检查是否有与node3的连接(无论方向)
|
362 |
+
if (edge[0] == node2_id and edge[1] == node3_id) or (
|
363 |
+
edge[0] == node3_id and edge[1] == node2_id
|
364 |
+
):
|
365 |
has_connection_with_node3 = True
|
366 |
+
|
367 |
+
assert (
|
368 |
+
has_connection_with_node1
|
369 |
+
), f"节点 {node2_id} 的边列表中应包含与 {node1_id} 的连接"
|
370 |
+
assert (
|
371 |
+
has_connection_with_node3
|
372 |
+
), f"节点 {node2_id} 的边列表中应包含与 {node3_id} 的连接"
|
373 |
print(f"无向图特性验证成功:节点 {node2_id} 的边列表包含所有相关的边")
|
374 |
|
375 |
# 5. 测试 get_all_labels - 获取所有标签
|
|
|
407 |
edge_props = await storage.get_edge(node2_id, node3_id)
|
408 |
print(f"删除后查询边属性 {node2_id} -> {node3_id}: {edge_props}")
|
409 |
assert edge_props is None, f"�� {node2_id} -> {node3_id} 应已被删除"
|
410 |
+
|
411 |
# 8.1 验证删除边的无向图特性
|
412 |
print(f"== 验证删除边的无向图特性: {node3_id} -> {node2_id}")
|
413 |
reverse_edge_props = await storage.get_edge(node3_id, node2_id)
|
414 |
print(f"删除后查询反向边属性 {node3_id} -> {node2_id}: {reverse_edge_props}")
|
415 |
+
assert (
|
416 |
+
reverse_edge_props is None
|
417 |
+
), f"反向边 {node3_id} -> {node2_id} 也应被删除,无向图特性验证失败"
|
418 |
print("无向图特性验证成功:删除一个方向的边后,反向边也被删除")
|
419 |
|
420 |
# 9. 测试 remove_nodes - 批量删除节点
|
|
|
673 |
edges_dict[(node3_id, node4_id)]["relationship"]
|
674 |
== edge5_data["relationship"]
|
675 |
), f"边 {node3_id} -> {node4_id} 关系不匹配"
|
676 |
+
|
677 |
# 5.1 测试反向边的批量获取 - 验证无向图特性
|
678 |
print("== 测试反向边的批量获取")
|
679 |
# 创建反向边的字典列表
|
680 |
reverse_edge_dicts = [{"src": tgt, "tgt": src} for src, tgt in edges]
|
681 |
reverse_edges_dict = await storage.get_edges_batch(reverse_edge_dicts)
|
682 |
print(f"批量获取反向边属性结果: {reverse_edges_dict.keys()}")
|
683 |
+
assert (
|
684 |
+
len(reverse_edges_dict) == 3
|
685 |
+
), f"应返回3条反向边的属性,实际返回 {len(reverse_edges_dict)} 条"
|
686 |
+
|
687 |
# 验证正向和反向边的属性是否一致
|
688 |
for (src, tgt), props in edges_dict.items():
|
689 |
+
assert (
|
690 |
+
tgt,
|
691 |
+
src,
|
692 |
+
) in reverse_edges_dict, f"反向边 {tgt} -> {src} 应在返回结果中"
|
693 |
+
assert (
|
694 |
+
props == reverse_edges_dict[(tgt, src)]
|
695 |
+
), f"边 {src} -> {tgt} 和反向边 {tgt} -> {src} 的属性不一致"
|
696 |
+
|
697 |
print("无向图特性验证成功:批量获取的正向和反向边属性一致")
|
698 |
|
699 |
# 6. 测试 get_nodes_edges_batch - 批量获取多个节点的所有边
|
|
|
711 |
assert (
|
712 |
len(nodes_edges[node3_id]) == 3
|
713 |
), f"{node3_id} 应有3条边,实际有 {len(nodes_edges[node3_id])} 条"
|
714 |
+
|
715 |
# 6.1 验证批量获取节点边的无向图特性
|
716 |
print("== 验证批量获取节点边的无向图特性")
|
717 |
+
|
718 |
# 检查节点1的边是否包含所有相关的边(无论方向)
|
719 |
+
node1_outgoing_edges = [
|
720 |
+
(src, tgt) for src, tgt in nodes_edges[node1_id] if src == node1_id
|
721 |
+
]
|
722 |
+
node1_incoming_edges = [
|
723 |
+
(src, tgt) for src, tgt in nodes_edges[node1_id] if tgt == node1_id
|
724 |
+
]
|
725 |
print(f"节点 {node1_id} 的出边: {node1_outgoing_edges}")
|
726 |
print(f"节点 {node1_id} 的入边: {node1_incoming_edges}")
|
727 |
+
|
728 |
# 检查是否包含到机器学习、自然语言处理和计算机视觉的边
|
729 |
has_edge_to_node2 = any(tgt == node2_id for _, tgt in node1_outgoing_edges)
|
730 |
has_edge_to_node4 = any(tgt == node4_id for _, tgt in node1_outgoing_edges)
|
731 |
has_edge_to_node5 = any(tgt == node5_id for _, tgt in node1_outgoing_edges)
|
732 |
+
|
733 |
assert has_edge_to_node2, f"节点 {node1_id} 的边列表中应包含到 {node2_id} 的边"
|
734 |
assert has_edge_to_node4, f"节点 {node1_id} 的边列表中应包含到 {node4_id} 的边"
|
735 |
assert has_edge_to_node5, f"节点 {node1_id} 的边列表中应包含到 {node5_id} 的边"
|
736 |
+
|
737 |
# 检查节点3的边是否包含所有相关的边(无论方向)
|
738 |
+
node3_outgoing_edges = [
|
739 |
+
(src, tgt) for src, tgt in nodes_edges[node3_id] if src == node3_id
|
740 |
+
]
|
741 |
+
node3_incoming_edges = [
|
742 |
+
(src, tgt) for src, tgt in nodes_edges[node3_id] if tgt == node3_id
|
743 |
+
]
|
744 |
print(f"节点 {node3_id} 的出边: {node3_outgoing_edges}")
|
745 |
print(f"节点 {node3_id} 的入边: {node3_incoming_edges}")
|
746 |
+
|
747 |
# 检查是否包含从机器学习来的边,以及到自然语言处理和计算机视觉的��
|
748 |
has_edge_from_node2 = any(src == node2_id for src, _ in node3_incoming_edges)
|
749 |
has_edge_to_node4 = any(tgt == node4_id for _, tgt in node3_outgoing_edges)
|
750 |
has_edge_to_node5 = any(tgt == node5_id for _, tgt in node3_outgoing_edges)
|
751 |
+
|
752 |
+
assert (
|
753 |
+
has_edge_from_node2
|
754 |
+
), f"节点 {node3_id} 的边列表中应包含从 {node2_id} 来的边"
|
755 |
assert has_edge_to_node4, f"节点 {node3_id} 的边列表中应包含到 {node4_id} 的边"
|
756 |
assert has_edge_to_node5, f"节点 {node3_id} 的边列表中应包含到 {node5_id} 的边"
|
757 |
+
|
758 |
print("无向图特性验证成功:批量获取的节点边包含所有相关的边(无论方向)")
|
759 |
|
760 |
# 7. 清理数据
|
|
|
822 |
|
823 |
# 2. 测试插入边后的无向图特性
|
824 |
print("\n== 测试插入边后的无向图特性")
|
825 |
+
|
826 |
# 插入边1: 计算机科学 -> 数据结构
|
827 |
edge1_data = {
|
828 |
"relationship": "包含",
|
|
|
831 |
}
|
832 |
print(f"插入边1: {node1_id} -> {node2_id}")
|
833 |
await storage.upsert_edge(node1_id, node2_id, edge1_data)
|
834 |
+
|
835 |
# 验证正向查询
|
836 |
forward_edge = await storage.get_edge(node1_id, node2_id)
|
837 |
print(f"正向边属性: {forward_edge}")
|
838 |
assert forward_edge is not None, f"未能读取正向边属性: {node1_id} -> {node2_id}"
|
839 |
+
|
840 |
# 验证反向查询
|
841 |
reverse_edge = await storage.get_edge(node2_id, node1_id)
|
842 |
print(f"反向边属性: {reverse_edge}")
|
843 |
assert reverse_edge is not None, f"未能读取反向边属性: {node2_id} -> {node1_id}"
|
844 |
+
|
845 |
# 验证正向和反向边属性是否一致
|
846 |
+
assert (
|
847 |
+
forward_edge == reverse_edge
|
848 |
+
), "正向和反向边属性不一致,无向图特性验证失败"
|
849 |
print("无向图特性验证成功:正向和反向边属性一致")
|
850 |
+
|
851 |
# 3. 测试边的度数的无向图特性
|
852 |
print("\n== 测试边的度数的无向图特性")
|
853 |
+
|
854 |
# 插入边2: 计算机科学 -> 算法
|
855 |
edge2_data = {
|
856 |
"relationship": "包含",
|
|
|
859 |
}
|
860 |
print(f"插入边2: {node1_id} -> {node3_id}")
|
861 |
await storage.upsert_edge(node1_id, node3_id, edge2_data)
|
862 |
+
|
863 |
# 验证正向和反向边的度数
|
864 |
forward_degree = await storage.edge_degree(node1_id, node2_id)
|
865 |
reverse_degree = await storage.edge_degree(node2_id, node1_id)
|
866 |
print(f"正向边 {node1_id} -> {node2_id} 的度数: {forward_degree}")
|
867 |
print(f"反向边 {node2_id} -> {node1_id} 的度数: {reverse_degree}")
|
868 |
+
assert (
|
869 |
+
forward_degree == reverse_degree
|
870 |
+
), "正向和反向边的度数不一致,无向图特性验证失败"
|
871 |
print("无向图特性验证成功:正向和反向边的度数一致")
|
872 |
+
|
873 |
# 4. 测试删除边的无向图特性
|
874 |
print("\n== 测试删除边的无向图特性")
|
875 |
+
|
876 |
# 删除正向边
|
877 |
print(f"删除边: {node1_id} -> {node2_id}")
|
878 |
await storage.remove_edges([(node1_id, node2_id)])
|
879 |
+
|
880 |
# 验证正向边是否被删除
|
881 |
forward_edge = await storage.get_edge(node1_id, node2_id)
|
882 |
print(f"删除后查询正向边属性 {node1_id} -> {node2_id}: {forward_edge}")
|
883 |
assert forward_edge is None, f"边 {node1_id} -> {node2_id} 应已被删除"
|
884 |
+
|
885 |
# 验证反向边是否也被删除
|
886 |
reverse_edge = await storage.get_edge(node2_id, node1_id)
|
887 |
print(f"删除后查询反向边属性 {node2_id} -> {node1_id}: {reverse_edge}")
|
888 |
+
assert (
|
889 |
+
reverse_edge is None
|
890 |
+
), f"反向边 {node2_id} -> {node1_id} 也应被删除,无向图特性验证失败"
|
891 |
print("无向图特性验证成功:删除一个方向的边后,反向边也被删除")
|
892 |
+
|
893 |
# 5. 测试批量操作中的无向图特性
|
894 |
print("\n== 测试批量操作中的无向图特性")
|
895 |
+
|
896 |
# 重新插入边
|
897 |
await storage.upsert_edge(node1_id, node2_id, edge1_data)
|
898 |
+
|
899 |
# 批量获取边属性
|
900 |
+
edge_dicts = [
|
901 |
+
{"src": node1_id, "tgt": node2_id},
|
902 |
+
{"src": node1_id, "tgt": node3_id},
|
903 |
+
]
|
904 |
+
reverse_edge_dicts = [
|
905 |
+
{"src": node2_id, "tgt": node1_id},
|
906 |
+
{"src": node3_id, "tgt": node1_id},
|
907 |
+
]
|
908 |
+
|
909 |
edges_dict = await storage.get_edges_batch(edge_dicts)
|
910 |
reverse_edges_dict = await storage.get_edges_batch(reverse_edge_dicts)
|
911 |
+
|
912 |
print(f"批量获取正向边属性结果: {edges_dict.keys()}")
|
913 |
print(f"批量获取反向边属性结果: {reverse_edges_dict.keys()}")
|
914 |
+
|
915 |
# 验证正向和反向边的属性是否一致
|
916 |
for (src, tgt), props in edges_dict.items():
|
917 |
+
assert (
|
918 |
+
tgt,
|
919 |
+
src,
|
920 |
+
) in reverse_edges_dict, f"反向边 {tgt} -> {src} 应在返回结果中"
|
921 |
+
assert (
|
922 |
+
props == reverse_edges_dict[(tgt, src)]
|
923 |
+
), f"边 {src} -> {tgt} 和反向边 {tgt} -> {src} 的属性不一致"
|
924 |
+
|
925 |
print("无向图特性验证成功:批量获取的正向和反向边属性一致")
|
926 |
+
|
927 |
# 6. 测试批量获取节点边的无向图特性
|
928 |
print("\n== 测试批量获取节点边的无向图特性")
|
929 |
+
|
930 |
nodes_edges = await storage.get_nodes_edges_batch([node1_id, node2_id])
|
931 |
print(f"批量获取节点边结果: {nodes_edges.keys()}")
|
932 |
+
|
933 |
# 检查节点1的边是否包含所有相关的边(无论方向)
|
934 |
node1_edges = nodes_edges[node1_id]
|
935 |
node2_edges = nodes_edges[node2_id]
|
936 |
+
|
937 |
# 检查节点1是否有到节点2和节点3的边
|
938 |
+
has_edge_to_node2 = any(
|
939 |
+
(src == node1_id and tgt == node2_id) for src, tgt in node1_edges
|
940 |
+
)
|
941 |
+
has_edge_to_node3 = any(
|
942 |
+
(src == node1_id and tgt == node3_id) for src, tgt in node1_edges
|
943 |
+
)
|
944 |
+
|
945 |
assert has_edge_to_node2, f"节点 {node1_id} 的边列表中应包含到 {node2_id} 的边"
|
946 |
assert has_edge_to_node3, f"节点 {node1_id} 的边列表中应包含到 {node3_id} 的边"
|
947 |
+
|
948 |
# 检查节点2是否有到节点1的边
|
949 |
+
has_edge_to_node1 = any(
|
950 |
+
(src == node2_id and tgt == node1_id)
|
951 |
+
or (src == node1_id and tgt == node2_id)
|
952 |
+
for src, tgt in node2_edges
|
953 |
+
)
|
954 |
+
assert (
|
955 |
+
has_edge_to_node1
|
956 |
+
), f"节点 {node2_id} 的边列表中应包含与 {node1_id} 的连接"
|
957 |
+
|
958 |
print("无向图特性验证成功:批量获取的节点边包含所有相关的边(无论方向)")
|
959 |
|
960 |
# 7. 清理数据
|
|
|
1032 |
if advanced_result:
|
1033 |
ASCIIColors.cyan("\n=== 开始批量操作测试 ===")
|
1034 |
batch_result = await test_graph_batch_operations(storage)
|
1035 |
+
|
1036 |
if batch_result:
|
1037 |
ASCIIColors.cyan("\n=== 开始无向图特性测试 ===")
|
1038 |
await test_graph_undirected_property(storage)
|