-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Optimize Python session result decoding #18638
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2990200
d9c053b
d8ec8c1
076547c
71068af
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,18 +18,18 @@ | |
|
|
||
| # for package | ||
| import logging | ||
| from collections import deque | ||
| from typing import Optional | ||
|
|
||
| import numpy as np | ||
| import pandas as pd | ||
| from thrift.transport import TTransport | ||
|
|
||
| from iotdb.thrift.rpc.IClientRPCService import TSFetchResultsReq, TSCloseOperationReq | ||
| from iotdb.thrift.rpc.IClientRPCService import TSCloseOperationReq, TSFetchResultsReq | ||
| from iotdb.tsfile.utils.date_utils import parse_int_to_date | ||
| from iotdb.tsfile.utils.tsblock_serde import deserialize | ||
| from iotdb.utils.exception import IoTDBConnectionException | ||
| from iotdb.utils.IoTDBConstants import TSDataType | ||
| from iotdb.utils.rpc_utils import verify_success, convert_to_timestamp | ||
| from iotdb.utils.rpc_utils import convert_to_timestamp, verify_success | ||
| from thrift.transport import TTransport | ||
|
|
||
| logger = logging.getLogger("IoTDB") | ||
| TIMESTAMP_STR = "Time" | ||
|
|
@@ -122,11 +122,13 @@ def __init__( | |
| self.__zone_id = zone_id | ||
| self.__time_precision = time_precision | ||
| self.__df_buffer = None # Buffer for streaming DataFrames | ||
| self.__row_buffer = deque() | ||
|
|
||
| def close(self): | ||
| if self.__is_closed: | ||
| return | ||
| self.__df_buffer = None # Clean up streaming DataFrame buffer | ||
| self.__row_buffer.clear() | ||
| if self.__client is not None: | ||
| try: | ||
| status = self.__client.closeOperation( | ||
|
|
@@ -148,16 +150,74 @@ def close(self): | |
| self.__client = None | ||
|
|
||
| def next(self): | ||
| if not self.has_cached_data_frame: | ||
| self.construct_one_data_frame() | ||
| if self.has_cached_data_frame: | ||
| return True | ||
| if self.__empty_resultSet: | ||
| return False | ||
| if self.__more_data and self.fetch_results(): | ||
| self.construct_one_data_frame() | ||
| return True | ||
| return False | ||
| return bool(self.__row_buffer) or self._fill_row_buffer() | ||
|
|
||
| def _pop_row(self): | ||
| if not self.next(): | ||
| return None | ||
| return self.__row_buffer.popleft() | ||
|
|
||
| def _pop_rows(self, max_rows=None): | ||
| rows = [] | ||
| while max_rows is None or len(rows) < max_rows: | ||
| if not self.__row_buffer and not self._fill_row_buffer(): | ||
| break | ||
| remaining = ( | ||
| len(self.__row_buffer) | ||
| if max_rows is None | ||
| else min(len(self.__row_buffer), max_rows - len(rows)) | ||
| ) | ||
| rows.extend(self.__row_buffer.popleft() for _ in range(remaining)) | ||
| return rows | ||
|
|
||
| def _fill_row_buffer(self): | ||
| while not self.__row_buffer: | ||
| while self.__query_result is not None and self.__query_result_index < len( | ||
| self.__query_result | ||
| ): | ||
| block = self.__query_result[self.__query_result_index] | ||
| self.__query_result[self.__query_result_index] = None | ||
| self.__query_result_index += 1 | ||
| self.__row_buffer.extend(self._deserialize_rows(block)) | ||
| if self.__row_buffer: | ||
| return True | ||
| if self.__empty_resultSet or not self.__more_data: | ||
| return False | ||
| if not self.fetch_results(): | ||
| return False | ||
| return True | ||
|
|
||
| def _deserialize_rows(self, serialized_block): | ||
| time_array, column_arrays, null_indicators, row_count = deserialize( | ||
| memoryview(serialized_block) | ||
| ) | ||
| columns = [] | ||
| if not self.ignore_timestamp: | ||
| columns.append(iter(time_array.tolist())) | ||
| for location in self.__column_index_2_tsblock_column_index_list: | ||
| if location < 0: | ||
| continue | ||
| columns.append( | ||
| self._column_values( | ||
| column_arrays[location], | ||
| null_indicators[location], | ||
| row_count, | ||
| self.__data_type_for_tsblock_column[location], | ||
| ) | ||
| ) | ||
| return list(zip(*columns)) if columns else [tuple() for _ in range(row_count)] | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A TSBlock is column-oriented. For example, |
||
|
|
||
| @staticmethod | ||
| def _column_values(values, nulls, row_count, data_type): | ||
| source = values.tolist() if hasattr(values, "tolist") else list(values) | ||
| if nulls is None: | ||
| return iter(source) | ||
| if data_type == TSDataType.BOOLEAN and len(source) == row_count: | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For example, Boolean |
||
| return ( | ||
| None if nulls[index] else value for index, value in enumerate(source) | ||
| ) | ||
| source_values = iter(source) | ||
| return (None if is_null else next(source_values) for is_null in nulls) | ||
|
|
||
| def construct_one_data_frame(self): | ||
| if self.has_cached_data_frame or self.__query_result is None: | ||
|
|
@@ -233,7 +293,7 @@ def construct_one_data_frame(self): | |
| self.has_cached_data_frame = True | ||
|
|
||
| def has_cached_result(self): | ||
| return self.has_cached_data_frame | ||
| return bool(self.__row_buffer) or self.has_cached_data_frame | ||
|
|
||
| def _has_next_result_set(self): | ||
| if (self.__query_result is not None) and ( | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Example: for 50,000 rows with
fetch_size=10,000, this method drains the current RPC page into the row buffer first. It callsfetch_results()for the next page only after that buffer is empty.