Skip to content

Commit ecf526a

Browse files
authored
Python: fix swapped session and statement IDs in query result sets (#18842)
1 parent 17b95c8 commit ecf526a

2 files changed

Lines changed: 196 additions & 64 deletions

File tree

‎iotdb-client/client-py/iotdb/Session.py‎

Lines changed: 64 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1627,22 +1627,22 @@ def execute_query_statement(self, sql, timeout=0):
16271627

16281628
rpc_utils.verify_success(resp.status)
16291629
return SessionDataSet(
1630-
sql,
1631-
resp.columns,
1632-
resp.dataTypeList,
1633-
resp.columnNameIndexMap,
1634-
resp.queryId,
1635-
self.__session_id,
1636-
self.__client,
1637-
self.__statement_id,
1638-
resp.queryResult,
1639-
resp.ignoreTimeStamp,
1640-
timeout,
1641-
resp.moreData,
1642-
self.__fetch_size,
1643-
self.__zone_id,
1644-
self.__time_precision,
1645-
resp.columnIndex2TsBlockColumnIndexList,
1630+
sql=sql,
1631+
column_name_list=resp.columns,
1632+
column_type_list=resp.dataTypeList,
1633+
column_name_index=resp.columnNameIndexMap,
1634+
query_id=resp.queryId,
1635+
statement_id=self.__statement_id,
1636+
client=self.__client,
1637+
session_id=self.__session_id,
1638+
query_result=resp.queryResult,
1639+
ignore_timestamp=resp.ignoreTimeStamp,
1640+
time_out=timeout,
1641+
more_data=resp.moreData,
1642+
fetch_size=self.__fetch_size,
1643+
zone_id=self.__zone_id,
1644+
time_precision=self.__time_precision,
1645+
column_index_2_tsblock_column_index_list=resp.columnIndex2TsBlockColumnIndexList,
16461646
)
16471647

16481648
def execute_non_query_statement(self, sql):
@@ -1698,22 +1698,22 @@ def execute_statement(self, sql: str, timeout=0):
16981698
rpc_utils.verify_success(resp.status)
16991699
if resp.columns:
17001700
return SessionDataSet(
1701-
sql,
1702-
resp.columns,
1703-
resp.dataTypeList,
1704-
resp.columnNameIndexMap,
1705-
resp.queryId,
1706-
self.__session_id,
1707-
self.__client,
1708-
self.__statement_id,
1709-
resp.queryResult,
1710-
resp.ignoreTimeStamp,
1711-
timeout,
1712-
resp.moreData,
1713-
self.__fetch_size,
1714-
self.__zone_id,
1715-
self.__time_precision,
1716-
resp.columnIndex2TsBlockColumnIndexList,
1701+
sql=sql,
1702+
column_name_list=resp.columns,
1703+
column_type_list=resp.dataTypeList,
1704+
column_name_index=resp.columnNameIndexMap,
1705+
query_id=resp.queryId,
1706+
statement_id=self.__statement_id,
1707+
client=self.__client,
1708+
session_id=self.__session_id,
1709+
query_result=resp.queryResult,
1710+
ignore_timestamp=resp.ignoreTimeStamp,
1711+
time_out=timeout,
1712+
more_data=resp.moreData,
1713+
fetch_size=self.__fetch_size,
1714+
zone_id=self.__zone_id,
1715+
time_precision=self.__time_precision,
1716+
column_index_2_tsblock_column_index_list=resp.columnIndex2TsBlockColumnIndexList,
17171717
)
17181718
else:
17191719
return None
@@ -1861,22 +1861,22 @@ def execute_raw_data_query(
18611861
raise IoTDBConnectionException(self.connection_error_msg()) from None
18621862
rpc_utils.verify_success(resp.status)
18631863
return SessionDataSet(
1864-
"",
1865-
resp.columns,
1866-
resp.dataTypeList,
1867-
resp.columnNameIndexMap,
1868-
resp.queryId,
1869-
self.__session_id,
1870-
self.__client,
1871-
self.__statement_id,
1872-
resp.queryResult,
1873-
resp.ignoreTimeStamp,
1874-
0,
1875-
resp.moreData,
1876-
self.__fetch_size,
1877-
self.__zone_id,
1878-
self.__time_precision,
1879-
resp.columnIndex2TsBlockColumnIndexList,
1864+
sql="",
1865+
column_name_list=resp.columns,
1866+
column_type_list=resp.dataTypeList,
1867+
column_name_index=resp.columnNameIndexMap,
1868+
query_id=resp.queryId,
1869+
statement_id=self.__statement_id,
1870+
client=self.__client,
1871+
session_id=self.__session_id,
1872+
query_result=resp.queryResult,
1873+
ignore_timestamp=resp.ignoreTimeStamp,
1874+
time_out=0,
1875+
more_data=resp.moreData,
1876+
fetch_size=self.__fetch_size,
1877+
zone_id=self.__zone_id,
1878+
time_precision=self.__time_precision,
1879+
column_index_2_tsblock_column_index_list=resp.columnIndex2TsBlockColumnIndexList,
18801880
)
18811881

18821882
def execute_last_data_query(self, paths: list, last_time: int) -> SessionDataSet:
@@ -1908,22 +1908,22 @@ def execute_last_data_query(self, paths: list, last_time: int) -> SessionDataSet
19081908
raise IoTDBConnectionException(self.connection_error_msg()) from None
19091909
rpc_utils.verify_success(resp.status)
19101910
return SessionDataSet(
1911-
"",
1912-
resp.columns,
1913-
resp.dataTypeList,
1914-
resp.columnNameIndexMap,
1915-
resp.queryId,
1916-
self.__statement_id,
1917-
self.__client,
1918-
self.__session_id,
1919-
resp.queryResult,
1920-
resp.ignoreTimeStamp,
1921-
0,
1922-
resp.moreData,
1923-
self.__fetch_size,
1924-
self.__zone_id,
1925-
self.__time_precision,
1926-
resp.columnIndex2TsBlockColumnIndexList,
1911+
sql="",
1912+
column_name_list=resp.columns,
1913+
column_type_list=resp.dataTypeList,
1914+
column_name_index=resp.columnNameIndexMap,
1915+
query_id=resp.queryId,
1916+
statement_id=self.__statement_id,
1917+
client=self.__client,
1918+
session_id=self.__session_id,
1919+
query_result=resp.queryResult,
1920+
ignore_timestamp=resp.ignoreTimeStamp,
1921+
time_out=0,
1922+
more_data=resp.moreData,
1923+
fetch_size=self.__fetch_size,
1924+
zone_id=self.__zone_id,
1925+
time_precision=self.__time_precision,
1926+
column_index_2_tsblock_column_index_list=resp.columnIndex2TsBlockColumnIndexList,
19271927
)
19281928

19291929
def insert_string_records_of_one_device(
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
#
18+
19+
import struct
20+
from unittest.mock import Mock
21+
22+
import pytest
23+
from thrift.transport import TTransport
24+
25+
from iotdb.Session import Session, SessionConnection
26+
from iotdb.thrift.common.ttypes import TSStatus
27+
from iotdb.thrift.rpc.IClientRPCService import Client
28+
from iotdb.thrift.rpc.ttypes import TSExecuteStatementResp, TSFetchResultsResp
29+
30+
31+
def int32_block(timestamp, value):
32+
# One INT32 value column, one row, plain INT64/INT32 encodings, no nulls.
33+
return struct.pack(">iBiBB?q?i", 1, 1, 1, 2, 1, False, timestamp, False, value)
34+
35+
36+
@pytest.mark.parametrize(
37+
"method,args,rpc_method",
38+
[
39+
(
40+
"execute_query_statement",
41+
("select s from root.test.d",),
42+
"executeQueryStatementV2",
43+
),
44+
(
45+
"execute_statement",
46+
("select s from root.test.d",),
47+
"executeStatementV2",
48+
),
49+
(
50+
"execute_raw_data_query",
51+
(["root.test.d.s"], 0, 10),
52+
"executeRawDataQueryV2",
53+
),
54+
(
55+
"execute_last_data_query",
56+
(["root.test.d.s"], 0),
57+
"executeLastDataQueryV2",
58+
),
59+
],
60+
)
61+
@pytest.mark.parametrize("reconnect", [False, True])
62+
@pytest.mark.parametrize("early_close", [False, True])
63+
def test_query_fetch_and_close_keep_connection_ids(
64+
monkeypatch, method, args, rpc_method, reconnect, early_close
65+
):
66+
# The two generators can advance independently; equal IDs hide a swap.
67+
initial_ids = (101, 202)
68+
active_ids = (404, 505) if reconnect else initial_ids
69+
query_id = 303
70+
client = Mock(spec=Client)
71+
72+
def execute(request):
73+
assert (request.sessionId, request.statementId) == active_ids
74+
return TSExecuteStatementResp(
75+
status=TSStatus(code=200),
76+
queryId=query_id,
77+
columns=["root.test.d.s"],
78+
dataTypeList=["INT32"],
79+
columnNameIndexMap={"root.test.d.s": 0},
80+
queryResult=[int32_block(1, 42)],
81+
ignoreTimeStamp=False,
82+
moreData=True,
83+
)
84+
85+
def fetch(request):
86+
assert (request.sessionId, request.statementId) == active_ids
87+
assert request.queryId == query_id
88+
return TSFetchResultsResp(
89+
status=TSStatus(code=200),
90+
hasResultSet=True,
91+
queryResult=[int32_block(2, 43)],
92+
moreData=False,
93+
)
94+
95+
def close(request):
96+
assert (request.sessionId, request.statementId) == active_ids
97+
assert request.queryId == query_id
98+
return TSStatus(code=200)
99+
100+
getattr(client, rpc_method).side_effect = execute
101+
client.fetchResultsV2.side_effect = fetch
102+
client.closeOperation.side_effect = close
103+
connections = []
104+
if reconnect:
105+
failed_client = Mock(spec=Client)
106+
107+
def fail(request):
108+
assert (request.sessionId, request.statementId) == initial_ids
109+
raise TTransport.TTransportException(message="Connection lost")
110+
111+
getattr(failed_client, rpc_method).side_effect = fail
112+
connections.append(SessionConnection(failed_client, Mock(), *initial_ids))
113+
connections.append(SessionConnection(client, Mock(), *active_ids))
114+
session = Session.init_from_node_urls(["127.0.0.1:6667"])
115+
init_connection = Mock(side_effect=connections)
116+
monkeypatch.setattr(session, "init_connection", init_connection)
117+
session.open()
118+
119+
with getattr(session, method)(*args) as data_set:
120+
row = data_set.next()
121+
assert row.get_timestamp() == 1
122+
assert row.get_fields()[0].get_int_value() == 42
123+
if not early_close:
124+
row = data_set.next()
125+
assert row.get_timestamp() == 2
126+
assert row.get_fields()[0].get_int_value() == 43
127+
assert data_set.next() is None
128+
129+
assert init_connection.call_count == (2 if reconnect else 1)
130+
getattr(client, rpc_method).assert_called_once()
131+
assert client.fetchResultsV2.call_count == (0 if early_close else 1)
132+
client.closeOperation.assert_called_once()

0 commit comments

Comments
 (0)