This is an automated email from the ASF dual-hosted git repository.
JackieTien97 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 ecf526ad73a Python: fix swapped session and statement IDs in query
result sets (#18842)
ecf526ad73a is described below
commit ecf526ad73ac5bd95491111ea18d71a8509e4bd9
Author: Colin Lee <[email protected]>
AuthorDate: Tue Oct 13 14:00:57 2026 +0800
Python: fix swapped session and statement IDs in query result sets (#18842)
---
iotdb-client/client-py/iotdb/Session.py | 128 ++++++++++----------
.../client-py/tests/unit/test_session_query_ids.py | 132 +++++++++++++++++++++
2 files changed, 196 insertions(+), 64 deletions(-)
diff --git a/iotdb-client/client-py/iotdb/Session.py
b/iotdb-client/client-py/iotdb/Session.py
index 5a4f2ff23e7..b9bccd05097 100644
--- a/iotdb-client/client-py/iotdb/Session.py
+++ b/iotdb-client/client-py/iotdb/Session.py
@@ -1627,22 +1627,22 @@ class Session(object):
rpc_utils.verify_success(resp.status)
return SessionDataSet(
- sql,
- resp.columns,
- resp.dataTypeList,
- resp.columnNameIndexMap,
- resp.queryId,
- self.__session_id,
- self.__client,
- self.__statement_id,
- resp.queryResult,
- resp.ignoreTimeStamp,
- timeout,
- resp.moreData,
- self.__fetch_size,
- self.__zone_id,
- self.__time_precision,
- resp.columnIndex2TsBlockColumnIndexList,
+ sql=sql,
+ column_name_list=resp.columns,
+ column_type_list=resp.dataTypeList,
+ column_name_index=resp.columnNameIndexMap,
+ query_id=resp.queryId,
+ statement_id=self.__statement_id,
+ client=self.__client,
+ session_id=self.__session_id,
+ query_result=resp.queryResult,
+ ignore_timestamp=resp.ignoreTimeStamp,
+ time_out=timeout,
+ more_data=resp.moreData,
+ fetch_size=self.__fetch_size,
+ zone_id=self.__zone_id,
+ time_precision=self.__time_precision,
+
column_index_2_tsblock_column_index_list=resp.columnIndex2TsBlockColumnIndexList,
)
def execute_non_query_statement(self, sql):
@@ -1698,22 +1698,22 @@ class Session(object):
rpc_utils.verify_success(resp.status)
if resp.columns:
return SessionDataSet(
- sql,
- resp.columns,
- resp.dataTypeList,
- resp.columnNameIndexMap,
- resp.queryId,
- self.__session_id,
- self.__client,
- self.__statement_id,
- resp.queryResult,
- resp.ignoreTimeStamp,
- timeout,
- resp.moreData,
- self.__fetch_size,
- self.__zone_id,
- self.__time_precision,
- resp.columnIndex2TsBlockColumnIndexList,
+ sql=sql,
+ column_name_list=resp.columns,
+ column_type_list=resp.dataTypeList,
+ column_name_index=resp.columnNameIndexMap,
+ query_id=resp.queryId,
+ statement_id=self.__statement_id,
+ client=self.__client,
+ session_id=self.__session_id,
+ query_result=resp.queryResult,
+ ignore_timestamp=resp.ignoreTimeStamp,
+ time_out=timeout,
+ more_data=resp.moreData,
+ fetch_size=self.__fetch_size,
+ zone_id=self.__zone_id,
+ time_precision=self.__time_precision,
+
column_index_2_tsblock_column_index_list=resp.columnIndex2TsBlockColumnIndexList,
)
else:
return None
@@ -1861,22 +1861,22 @@ class Session(object):
raise IoTDBConnectionException(self.connection_error_msg())
from None
rpc_utils.verify_success(resp.status)
return SessionDataSet(
- "",
- resp.columns,
- resp.dataTypeList,
- resp.columnNameIndexMap,
- resp.queryId,
- self.__session_id,
- self.__client,
- self.__statement_id,
- resp.queryResult,
- resp.ignoreTimeStamp,
- 0,
- resp.moreData,
- self.__fetch_size,
- self.__zone_id,
- self.__time_precision,
- resp.columnIndex2TsBlockColumnIndexList,
+ sql="",
+ column_name_list=resp.columns,
+ column_type_list=resp.dataTypeList,
+ column_name_index=resp.columnNameIndexMap,
+ query_id=resp.queryId,
+ statement_id=self.__statement_id,
+ client=self.__client,
+ session_id=self.__session_id,
+ query_result=resp.queryResult,
+ ignore_timestamp=resp.ignoreTimeStamp,
+ time_out=0,
+ more_data=resp.moreData,
+ fetch_size=self.__fetch_size,
+ zone_id=self.__zone_id,
+ time_precision=self.__time_precision,
+
column_index_2_tsblock_column_index_list=resp.columnIndex2TsBlockColumnIndexList,
)
def execute_last_data_query(self, paths: list, last_time: int) ->
SessionDataSet:
@@ -1908,22 +1908,22 @@ class Session(object):
raise IoTDBConnectionException(self.connection_error_msg())
from None
rpc_utils.verify_success(resp.status)
return SessionDataSet(
- "",
- resp.columns,
- resp.dataTypeList,
- resp.columnNameIndexMap,
- resp.queryId,
- self.__statement_id,
- self.__client,
- self.__session_id,
- resp.queryResult,
- resp.ignoreTimeStamp,
- 0,
- resp.moreData,
- self.__fetch_size,
- self.__zone_id,
- self.__time_precision,
- resp.columnIndex2TsBlockColumnIndexList,
+ sql="",
+ column_name_list=resp.columns,
+ column_type_list=resp.dataTypeList,
+ column_name_index=resp.columnNameIndexMap,
+ query_id=resp.queryId,
+ statement_id=self.__statement_id,
+ client=self.__client,
+ session_id=self.__session_id,
+ query_result=resp.queryResult,
+ ignore_timestamp=resp.ignoreTimeStamp,
+ time_out=0,
+ more_data=resp.moreData,
+ fetch_size=self.__fetch_size,
+ zone_id=self.__zone_id,
+ time_precision=self.__time_precision,
+
column_index_2_tsblock_column_index_list=resp.columnIndex2TsBlockColumnIndexList,
)
def insert_string_records_of_one_device(
diff --git a/iotdb-client/client-py/tests/unit/test_session_query_ids.py
b/iotdb-client/client-py/tests/unit/test_session_query_ids.py
new file mode 100644
index 00000000000..17ca6508ed9
--- /dev/null
+++ b/iotdb-client/client-py/tests/unit/test_session_query_ids.py
@@ -0,0 +1,132 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+import struct
+from unittest.mock import Mock
+
+import pytest
+from thrift.transport import TTransport
+
+from iotdb.Session import Session, SessionConnection
+from iotdb.thrift.common.ttypes import TSStatus
+from iotdb.thrift.rpc.IClientRPCService import Client
+from iotdb.thrift.rpc.ttypes import TSExecuteStatementResp, TSFetchResultsResp
+
+
+def int32_block(timestamp, value):
+ # One INT32 value column, one row, plain INT64/INT32 encodings, no nulls.
+ return struct.pack(">iBiBB?q?i", 1, 1, 1, 2, 1, False, timestamp, False,
value)
+
+
[email protected](
+ "method,args,rpc_method",
+ [
+ (
+ "execute_query_statement",
+ ("select s from root.test.d",),
+ "executeQueryStatementV2",
+ ),
+ (
+ "execute_statement",
+ ("select s from root.test.d",),
+ "executeStatementV2",
+ ),
+ (
+ "execute_raw_data_query",
+ (["root.test.d.s"], 0, 10),
+ "executeRawDataQueryV2",
+ ),
+ (
+ "execute_last_data_query",
+ (["root.test.d.s"], 0),
+ "executeLastDataQueryV2",
+ ),
+ ],
+)
[email protected]("reconnect", [False, True])
[email protected]("early_close", [False, True])
+def test_query_fetch_and_close_keep_connection_ids(
+ monkeypatch, method, args, rpc_method, reconnect, early_close
+):
+ # The two generators can advance independently; equal IDs hide a swap.
+ initial_ids = (101, 202)
+ active_ids = (404, 505) if reconnect else initial_ids
+ query_id = 303
+ client = Mock(spec=Client)
+
+ def execute(request):
+ assert (request.sessionId, request.statementId) == active_ids
+ return TSExecuteStatementResp(
+ status=TSStatus(code=200),
+ queryId=query_id,
+ columns=["root.test.d.s"],
+ dataTypeList=["INT32"],
+ columnNameIndexMap={"root.test.d.s": 0},
+ queryResult=[int32_block(1, 42)],
+ ignoreTimeStamp=False,
+ moreData=True,
+ )
+
+ def fetch(request):
+ assert (request.sessionId, request.statementId) == active_ids
+ assert request.queryId == query_id
+ return TSFetchResultsResp(
+ status=TSStatus(code=200),
+ hasResultSet=True,
+ queryResult=[int32_block(2, 43)],
+ moreData=False,
+ )
+
+ def close(request):
+ assert (request.sessionId, request.statementId) == active_ids
+ assert request.queryId == query_id
+ return TSStatus(code=200)
+
+ getattr(client, rpc_method).side_effect = execute
+ client.fetchResultsV2.side_effect = fetch
+ client.closeOperation.side_effect = close
+ connections = []
+ if reconnect:
+ failed_client = Mock(spec=Client)
+
+ def fail(request):
+ assert (request.sessionId, request.statementId) == initial_ids
+ raise TTransport.TTransportException(message="Connection lost")
+
+ getattr(failed_client, rpc_method).side_effect = fail
+ connections.append(SessionConnection(failed_client, Mock(),
*initial_ids))
+ connections.append(SessionConnection(client, Mock(), *active_ids))
+ session = Session.init_from_node_urls(["127.0.0.1:6667"])
+ init_connection = Mock(side_effect=connections)
+ monkeypatch.setattr(session, "init_connection", init_connection)
+ session.open()
+
+ with getattr(session, method)(*args) as data_set:
+ row = data_set.next()
+ assert row.get_timestamp() == 1
+ assert row.get_fields()[0].get_int_value() == 42
+ if not early_close:
+ row = data_set.next()
+ assert row.get_timestamp() == 2
+ assert row.get_fields()[0].get_int_value() == 43
+ assert data_set.next() is None
+
+ assert init_connection.call_count == (2 if reconnect else 1)
+ getattr(client, rpc_method).assert_called_once()
+ assert client.fetchResultsV2.call_count == (0 if early_close else 1)
+ client.closeOperation.assert_called_once()