NebulaGraph Python Client 5.3.0
Loading...
Searching...
No Matches
scan.py
1# Copyright 2025 vesoft-inc
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15from typing import Iterable, List, Literal, Optional, Sequence, Tuple
16
17from nebulagraph_python.client import NebulaBaseExecutor
18from nebulagraph_python.py_data_types import Edge, Node
19from nebulagraph_python.result_set import Record
21 get_edge_type,
22 get_node_type,
23)
25 get_graph_type_name,
26 get_home_graph_name,
27)
28
29
30def _scan_partition(
31 client: NebulaBaseExecutor,
32 scan_type: Literal["node", "edge"], # TODO: "edge_only"
33 partition_id: int,
34 graph_name: str,
35 type_name: str,
36 properties_list: Sequence[str],
37 batch_size: int = 1024,
38) -> Iterable[Record]:
39 """
40 Scan nodes/edges on graph in a given storage partition.
41
42 Args:
43 client: The client to use for the scan.
44 scan_type: The type of the scan. "node" or "edge".
45 partition_id: The partition id to scan.
46 graph_name: Filter the name of the graph.
47 type_name: Filter the name of the node/edge type.
48 properties_list: Return the properties of the node/edge.
49 batch_size: The batch size when scanning the nodes/edges.
50
51 Returns:
52 An iterable of records.
53 """
54 if scan_type == "node":
55 query = "CALL cursor_node_scan({{graph_name}}, {{type_name}}, {{properties_list}}, {{partition_id}}, {{cursor}}, {{batch_size}}) RETURN *"
56 elif scan_type == "edge":
57 query = "CALL cursor_edge_scan({{graph_name}}, {{type_name}}, {{properties_list}}, {{partition_id}}, {{cursor}}, {{batch_size}}) RETURN *"
58 # TODO: "edge_only"
59 # elif scan_type == "edge_only":
60 # raise ValueError(f"Invalid scan type: {scan_type}")
61 # query = "CALL cursor_edge_only_scan({{graph_name}}, {{type_name}}, {{properties_list}}, {{partition_id}}, {{cursor}}, {{batch_size}}) RETURN *"
62 else:
63 raise ValueError(f"Invalid scan type: {scan_type}")
64
65 cursor = ""
66 while True:
67 result = client.execute_py(
68 query,
69 stmt_args={
70 "graph_name": graph_name,
71 "type_name": type_name,
72 "properties_list": properties_list,
73 "partition_id": partition_id,
74 "batch_size": batch_size,
75 "cursor": cursor,
76 },
77 )
78 for row in result:
79 yield row
80 cursor = result.extra_info.cursor
81 if cursor == "":
82 break
83
84
85def scan(
86 client: NebulaBaseExecutor,
87 scan_type: Literal["node", "edge"], # TODO: "edge_only"
88 graph_name: str | None = None,
89 type_name: str | None = None,
90 graph_type_name: Optional[str] = None,
91 properties_list: Optional[Sequence[str]] = None,
92 batch_size: int = 1024,
93) -> Iterable[Record]:
94 """
95 Scan nodes/edges on graph.
96
97 Args:
98 client: The client to use for the scan.
99 scan_type: The type of the scan. "node" or "edge".
100 graph_name: Filter the name of the graph.
101 type_name: Filter the name of the node/edge type.
102 graph_type_name: Filter the name of the graph type.
103 properties_list: Return the properties of the node/edge. Default to None means all properties.
104 batch_size: The batch size when scanning the nodes/edges.
105
106 Returns:
107 An iterable of records.
108 """
109 graph_name = graph_name or get_home_graph_name(client)
110 if not type_name:
111 raise ValueError("type_name is required for scan")
112 graph_type_name = graph_type_name or get_graph_type_name(client, graph_name)
113 properties_list = (
114 properties_list
115 if properties_list is not None
116 else (
117 list(get_node_type(client, type_name, graph_type_name).properties.keys())
118 if scan_type == "node"
119 else list(
120 get_edge_type(client, type_name, graph_type_name).properties.keys()
121 )
122 )
123 )
124 partitions: List[int] = client.execute_py(
125 "CALL show_partitions() RETURN partition_id"
126 ).as_primitive_by_column()["partition_id"]
127 # Filter partitions 0
128 # TODO: Revisit it which may break between 5.0 and 5.1
129 partitions = [p for p in partitions if p != 0]
130
131 for partition_id in partitions:
132 for record in _scan_partition(
133 client=client,
134 scan_type=scan_type,
135 partition_id=partition_id,
136 graph_name=graph_name,
137 type_name=type_name,
138 properties_list=properties_list,
139 batch_size=batch_size,
140 ):
141 yield record
142
143
144def scan_nodes(
145 client: NebulaBaseExecutor,
146 *,
147 graph_name: str | None = None,
148 type_name: str,
149 graph_type_name: Optional[str] = None,
150 properties_list: Optional[Sequence[str]] = None,
151 batch_size: int = 1024,
152) -> Iterable[Node]:
153 graph_name = graph_name or get_home_graph_name(client)
154 if not type_name:
155 raise ValueError("type_name is required for scan_nodes")
156 graph_type_name = graph_type_name or get_graph_type_name(client, graph_name)
157 properties_list = (
158 properties_list
159 if properties_list is not None
160 else [
161 k
162 for k, v in get_node_type(
163 client, type_name, graph_type_name
164 ).properties.items()
165 if not v.data_type.startswith(
166 "VECTOR"
167 ) # TODO : TO BE FIXED! scan with vector properties will cause decoding error.
168 ]
169 )
170 """
171 TODO: scan with vector properties will cause error.
172 CREATE GRAPH TYPE test_scan_type {
173 NODE node1 ({n STRING PRIMARY KEY, emb1 VECTOR<3, FLOAT>})
174 }
175CREATE GRAPH test_scan test_scan_type
176SESSION SET GRAPH test_scan
177INSERT (@node1 {n: "foo", emb1: VECTOR<3, FLOAT>([1.0, 2.0, 3.0])})
178
179CALL {CALL show_partitions() RETURN collect(partition_id) AS parts GROUP BY ()}
180FOR part IN parts
181CALL cursor_node_scan("test_scan", "node1", ["n", "emb1"], part, "", 999) RETURN *
182"""
183 for record in scan(
184 client=client,
185 scan_type="node",
186 graph_name=graph_name,
187 type_name=type_name,
188 graph_type_name=graph_type_name,
189 properties_list=properties_list,
190 batch_size=batch_size,
191 ):
192 yield record["node"].as_node()
193
194
195def scan_edges(
196 client: NebulaBaseExecutor,
197 graph_name: str | None = None,
198 type_name: str | None = None,
199 graph_type_name: Optional[str] = None,
200 properties_list: Optional[Sequence[str]] = None,
201 batch_size: int = 1024,
202) -> Iterable[Tuple[Node, Edge, Node]]:
203 graph_name = graph_name or get_home_graph_name(client)
204 if not type_name:
205 raise ValueError("type_name is required for scan_edges")
206 graph_type_name = graph_type_name or get_graph_type_name(client, graph_name)
207 properties_list = (
208 properties_list
209 if properties_list is not None
210 else [
211 k
212 for k, v in get_edge_type(
213 client, type_name, graph_type_name
214 ).properties.items()
215 if not v.data_type.startswith(
216 "VECTOR"
217 ) # TODO : TO BE FIXED! scan with vector properties will cause decoding error.
218 ]
219 )
220 for record in scan(
221 client=client,
222 scan_type="edge",
223 graph_name=graph_name,
224 type_name=type_name,
225 graph_type_name=graph_type_name,
226 properties_list=properties_list,
227 batch_size=batch_size,
228 ):
229 yield (
230 record["src"].as_node(),
231 record["edge"].as_edge(),
232 record["dst"].as_node(),
233 )