17from typing
import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
41 """Stateful iterator over batched result data, matching Java's ResultTable.next() semantics.
43 Rows are decoded on-demand as next() is called, rather than all at once.
46 def __init__(self, table: Any):
47 if not isinstance(table, VectorResultTable):
52 graph_schemas = ResultGraphSchemas(table.meta.graph_schema)
53 time_zone_offset = table.meta.time_zone_offset
55 if table.meta.is_little_endian:
63 raise RuntimeError(
"the number of batch is not equal to numBatches")
65 self.
column_names = list(table.meta.row_type.column_names)
66 self.column_data_types: List[DataType] = []
67 value_type_parser = ValueTypeParser(self.
byte_order)
68 for col_type
in table.meta.row_type.column_types:
69 if not col_type.value_type:
70 raise ValueError(
"Invalid column type: empty value_type")
71 data_type = value_type_parser.get_data_type(
72 BytesReader(col_type.value_type),
74 self.column_data_types.append(data_type)
79 self._batch_index: int = 0
86 """Decode one row from the given batch at the specified index."""
88 decode_value_wrapper = self.
parser.decode_value_wrapper
89 get_vectors = batch.get_vectors
90 column_data_types = self.column_data_types
91 for i
in range(batch.get_vectors_count()):
92 value = decode_value_wrapper(
101 """Return the next decoded row, advancing internal state.
103 Mirrors Java ResultTable.next():
104 - Skip empty or exhausted batches automatically.
105 - Raises StopIteration when all rows have been consumed.
108 raise StopIteration(
"no more batch data")
110 current_batch_row_size = 0
119 self._batch_index += 1
121 raise StopIteration(
"no more batch data")
135 def __next__(self) -> Row:
140 column_names: List[str]
141 col_values: List[ValueWrapper]
142 mapping: Optional[Dict[str, int]]
144 def __init__(self, column_names: Optional[List[str]], row: Row):
148 if column_names
is None or row
is None or not row.values:
155 def _get_mapping(self) -> Dict[str, int]:
162 def __iter__(self) -> Iterator[Tuple[str, ValueWrapper]]:
165 def __str__(self) -> str:
167 return f
"ColumnName: {self.column_names}, Values: {value_strs}"
169 def get(self, key: Union[int, str]) -> ValueWrapper:
170 if isinstance(key, str):
173 except KeyError
as e:
175 f
"Cannot get field because the columnName '{key}' is not exists",
179 except IndexError
as e:
181 f
"Cannot get field because the key '{key}' out of range",
184 def __getitem__(self, key: Union[int, str]) -> ValueWrapper:
187 def values(self) -> List[ValueWrapper]:
190 def items(self) -> Iterator[Tuple[str, ValueWrapper]]:
194 def size(self) -> int:
197 def contains(self, column_name: str) -> bool:
200 def for_each(self, action):
204 def spliterator(self):
207 def as_primitive(self) -> Dict[str, Any]:
208 return {col: val.cast_primitive()
for col, val
in self.
items()}
212 column_names: List[str]
213 result_table: Optional[ResultTable]
217 plan_desc: PlanInfoNode
219 extra_info: ExtraInfo
221 def __init__(self, response: Any):
222 if not isinstance(response, ExecuteResponse):
223 raise InternalError(f
"got {type(response)} object for server's response")
225 if response.HasField(
"result"):
234 self._is_empty: bool = self.
sizesize == 0
239 if not response.HasField(
"status"):
245 if not response.HasField(
"summary"):
250 cursor=response.cursor.decode(
"utf-8"),
251 affected_nodes=response.summary.query_stats.num_affected_nodes,
252 affected_edges=response.summary.query_stats.num_affected_edges,
253 total_server_time_us=response.summary.elapsed_time.total_server_time_us,
254 build_time_us=response.summary.elapsed_time.build_time_us,
255 optimize_time_us=response.summary.elapsed_time.optimize_time_us,
256 serialize_time_us=response.summary.elapsed_time.serialize_time_us,
259 def raise_on_error(self) -> "ResultSet":
265 error_code = ErrorCode.UNKNOWN
274 """Return True when the result set contains no rows."""
275 return self._is_empty
282 """Return True if there are more rows to consume.
284 Mirrors Java ResultSet.hasNext().
291 """Decode and return the next row as a Record.
293 Mirrors Java ResultSet.next(): each call advances the internal
294 cursor and decodes exactly one row on demand.
298 StopIteration: when no more rows are available.
301 raise StopIteration(
"no more row record data")
312 def __iter__(self) -> "ResultSet":
315 def __next__(self) -> Record:
325 """Generator that yields all remaining Records.
327 Compatible with existing code that calls ``for record in result.records()``.
332 def __str__(self) -> str:
335 return f
"ColumnName: {self.column_names}, RowSize: {self.size}, Latency: {self.latency_us}"
337 def as_primitive_by_row(self) -> Iterable[Dict[str, Any]]:
339 yield record.as_primitive()
341 def as_primitive_by_column(self) -> Dict[str, List[Any]]:
344 for col, val
in record.as_primitive().items():
345 answer[col].append(val)
349 """Convert result set to pandas DataFrame."""
352 except ImportError
as e:
354 "pandas is required to use this method. Please install it using 'pip install pandas'",
358 raise RuntimeError(f
"Query failed: {self.status_message}")
366 for val
in record.values():
367 row.append(val.cast_primitive()
if val
is not None else None)
374 console: Optional[
"Console"] =
None,
375 style: str =
"table",
376 width: Optional[int] =
None,
378 max_width: Optional[int] =
None,
380 collapse_padding: bool =
False,
382 """Print query results in a formatted table or row-by-row format.
384 Return the string if console is not provided.
387 from io
import StringIO
390 from rich.console
import Console
391 from rich.table
import Table
392 except ImportError
as e:
394 "The 'rich' library is required to use this method. Please install it using 'pip install rich'.",
397 have_console: bool = console
is not None
398 console = console
or Console(file=StringIO(), force_terminal=
False)
401 console.print(f
"[bold red]Error:[/bold red] {self.status_message}")
404 return console.file.read()
407 console.print(
"[yellow]Empty result set[/yellow]")
410 return console.file.read()
415 console.print(f
"\n[bold blue]Row {row_num}[/bold blue]")
417 console.print(f
" [cyan]{col}:[/cyan] {val.cast_primitive()}")
423 header_style=
"bold cyan",
427 collapse_padding=collapse_padding,
432 table.add_column(header, max_width=max_width, overflow=
"fold")
435 table.add_row(*[str(v.cast_primitive())
for v
in record.values()])
439 console.print(
"\n[bold green]Summary[/bold green]")
440 console.print(f
"├── [green]Rows:[/green] {self.size}")
441 console.print(f
"└── [blue]Latency:[/blue] {self.latency_us}μs")
445 return console.file.read()
447 def one(self) -> Record:
450 def one_or_none(self) -> Optional[Record]:
453 except StopIteration:
458 style: str =
"table",
459 width: Optional[int] =
None,
461 max_width: Optional[int] =
None,
463 collapse_padding: bool =
False,
465 """Print the results directly to console with rich formatting."""
466 from rich.console
import Console
print(self, str style="table", Optional[int] width=None, int min_width=8, Optional[int] max_width=None, int padding=1, bool collapse_padding=False)
Optional[str] as_ascii_table(self, Optional["Console"] console=None, str style="table", Optional[int] width=None, int min_width=8, Optional[int] max_width=None, int padding=1, bool collapse_padding=False)