This is an automated email from the ASF dual-hosted git repository.

jt2594838 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 0f64600571b Optimize Python session result decoding (#18638)
0f64600571b is described below

commit 0f64600571b838fb03435afbc8c54953284c27b7
Author: Hongzhi Gao <[email protected]>
AuthorDate: Wed Sep 16 10:05:15 2026 +0800

    Optimize Python session result decoding (#18638)
    
    * Optimize Python session result decoding
    
    * Clarify Python result decoding logic
    
    * Revert "Clarify Python result decoding logic"
    
    This reverts commit d9c053b6050fd1aa99aaab771546738386751fb6.
    
    * Keep pandas imports in result decoding
    
    * Stream decoded columns into row tuples
---
 .../client-py/iotdb/utils/SessionDataSet.py        | 30 ++++----
 .../client-py/iotdb/utils/iotdb_rpc_dataset.py     | 90 ++++++++++++++++++----
 2 files changed, 88 insertions(+), 32 deletions(-)

diff --git a/iotdb-client/client-py/iotdb/utils/SessionDataSet.py 
b/iotdb-client/client-py/iotdb/utils/SessionDataSet.py
index 9c5b3ec06dd..3f92c78fb6a 100644
--- a/iotdb-client/client-py/iotdb/utils/SessionDataSet.py
+++ b/iotdb-client/client-py/iotdb/utils/SessionDataSet.py
@@ -18,15 +18,14 @@
 import logging
 from typing import Optional
 
+import pandas as pd
 from iotdb.utils.Field import Field
+from iotdb.utils.iotdb_rpc_dataset import IoTDBRpcDataSet
 
 # for package
 from iotdb.utils.IoTDBConstants import TSDataType
-from iotdb.utils.iotdb_rpc_dataset import IoTDBRpcDataSet
 from iotdb.utils.RowRecord import RowRecord
 
-import pandas as pd
-
 logger = logging.getLogger("IoTDB")
 
 
@@ -109,14 +108,14 @@ class SessionDataSet(object):
         return self.iotdb_rpc_data_set.next()
 
     def next(self):
-        if not self.iotdb_rpc_data_set.has_cached_data_frame:
-            if not self.has_next():
-                return None
-        return self.construct_row_record_from_data_frame()
-
-    def construct_row_record_from_data_frame(self):
-        df = self.iotdb_rpc_data_set.data_frame
-        row = df.iloc[self.row_index].to_list()
+        if not self.has_next():
+            return None
+        return self._construct_row_record()
+
+    def _construct_row_record(self):
+        row = self.iotdb_rpc_data_set._pop_row()
+        if row is None:
+            return None
         if self.iotdb_rpc_data_set.ignore_timestamp:
             for field, value in zip(self.__field_list, row):
                 field.value = value
@@ -133,14 +132,11 @@ class SessionDataSet(object):
                 row[0],
                 self.__field_list,
             )
-        self.row_index += 1
-        if self.row_index == len(df):
-            self.row_index = 0
-            self.iotdb_rpc_data_set.has_cached_data_frame = False
-            self.iotdb_rpc_data_set.data_frame = None
-
         return row_record
 
+    def construct_row_record_from_data_frame(self):
+        return self._construct_row_record()
+
     def close_operation_handle(self):
         self.iotdb_rpc_data_set.close()
 
diff --git a/iotdb-client/client-py/iotdb/utils/iotdb_rpc_dataset.py 
b/iotdb-client/client-py/iotdb/utils/iotdb_rpc_dataset.py
index 0edc76f68fd..bcb259c6f42 100644
--- a/iotdb-client/client-py/iotdb/utils/iotdb_rpc_dataset.py
+++ b/iotdb-client/client-py/iotdb/utils/iotdb_rpc_dataset.py
@@ -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 @@ class IoTDBRpcDataSet(object):
         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 @@ class IoTDBRpcDataSet(object):
             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)]
+
+    @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:
+            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 @@ class IoTDBRpcDataSet(object):
             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 (

Reply via email to