Skip to content

Commit c70277a

Browse files
committed
BUG#37627508: mysql/connector python fetchmany() has an off by one bug
when argument given as 1 This patch fixes the issue where c-extension based default cursor's fetchmany method gets stuck in an infinite loop when one result row at a time is being fetched. Change-Id: I2290dc1330c50d39fdb75617e7fcbf8a8cf7c6de
1 parent bd24fee commit c70277a

File tree

4 files changed

+65
-14
lines changed

4 files changed

+65
-14
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ v9.4.0
1313

1414
- BUG#37820231: Text based django ORM filters doesn't work with Connector/Python
1515
- BUG#37806057: Rename extra option (when installing wheel package) to install webauthn functionality dependencies
16+
- BUG#37627508: mysql/connector python fetchmany() has an off by one bug when argument given as 1
1617
- BUG#37047789: Python connector does not support Django enum
1718
- BUG#36452514: Missing version info resources
1819
- BUG#34950958: MySQL Python Connector doesn't work with ssh in the same process

mysql-connector-python/lib/mysql/connector/cursor_cext.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -704,17 +704,16 @@ def fetchmany(self, size: int = 1) -> List[RowType]:
704704
if size and self._connection.unread_result:
705705
rows.extend(self._connection.get_rows(size, raw=self._raw)[0])
706706

707-
if size:
708-
if self._connection.unread_result:
709-
self._nextrow = self._connection.get_row()
710-
if (
711-
self._nextrow
712-
and not self._nextrow[0]
713-
and not self._connection.more_results
714-
):
715-
self._connection.free_result()
716-
else:
717-
self._nextrow = (None, None)
707+
if self._connection.unread_result:
708+
self._nextrow = self._connection.get_row()
709+
if (
710+
self._nextrow
711+
and not self._nextrow[0]
712+
and not self._connection.more_results
713+
):
714+
self._connection.free_result()
715+
else:
716+
self._nextrow = (None, None)
718717

719718
if not rows:
720719
self._handle_eof()

mysql-connector-python/tests/cext/test_cext_cursor.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@
3737
import logging
3838
import sys
3939
import unittest
40-
import warnings
4140

4241
import tests
4342

43+
import mysql.connector
44+
4445
from mysql.connector import errorcode, errors
4546

4647
try:
@@ -1161,3 +1162,54 @@ def test_executemany(self):
11611162
self.assertEqual(len(rows), 4)
11621163
self.assertEqual(rows[0], dict(zip(self.column_names, self.exp)))
11631164
self.assertEqual(rows[1], dict(zip(self.column_names, self.exp)))
1165+
1166+
1167+
class BugOra37627508(tests.MySQLConnectorTests):
1168+
"""BUG#37627508: mysql/connector python fetchmany() has an off by one bug when argument given as 1
1169+
1170+
While single result is being fetched using the c-extension based cursor module's fetchmany() method,
1171+
the application crashes into a infinite loop of same results being returned by the cursor.
1172+
1173+
This patch fixes the issue by flushing out the cached result (next row) after it is being used up.
1174+
"""
1175+
1176+
table_name = "Bug37627508"
1177+
1178+
exp_fetchmany_result_1 = [[(1,)], [(2,)], [(3,)], [(4,)], [(5,)], []]
1179+
exp_fetchmany_result_2 = [[(1,), (2,)], [(3,), (4,)], [(5,)], []]
1180+
1181+
@classmethod
1182+
def setUpClass(cls):
1183+
with mysql.connector.connect(**tests.get_mysql_config()) as cnx:
1184+
cnx.cmd_query(f"CREATE TABLE IF NOT EXISTS {cls.table_name} (id INTEGER)")
1185+
cnx.cmd_query(f"INSERT INTO {cls.table_name} (id) VALUES (1), (2), (3), (4), (5)")
1186+
cnx.commit()
1187+
1188+
@classmethod
1189+
def tearDownClass(cls):
1190+
with mysql.connector.connect(**tests.get_mysql_config()) as cnx:
1191+
cnx.cmd_query(f"DROP TABLE IF EXISTS {cls.table_name}")
1192+
1193+
@tests.foreach_cnx()
1194+
def test_fetchmany_cext_resultsize_1(self):
1195+
with self.cnx.cursor() as cur:
1196+
cur.execute(f"SELECT * FROM {self.table_name}")
1197+
res = []
1198+
try:
1199+
for _ in range(6):
1200+
res.append(cur.fetchmany(1))
1201+
self.assertEqual(res, self.exp_fetchmany_result_1)
1202+
except Exception as e:
1203+
self.fail(e)
1204+
1205+
@tests.foreach_cnx()
1206+
def test_fetchmany_cext_resultsize_2(self):
1207+
with self.cnx.cursor() as cur:
1208+
cur.execute(f"SELECT * FROM {self.table_name}")
1209+
res = []
1210+
try:
1211+
for _ in range(4):
1212+
res.append(cur.fetchmany(2))
1213+
self.assertEqual(res, self.exp_fetchmany_result_2)
1214+
except Exception as e:
1215+
self.fail(e)

mysql-connector-python/tests/test_bugs.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
to be created first.
4242
"""
4343

44-
import asyncio
4544
import collections
4645
import gc
4746
import os
@@ -52,7 +51,7 @@
5251
import traceback
5352
import unittest
5453

55-
from datetime import date, datetime, time, timedelta, timezone
54+
from datetime import date, datetime, timedelta, timezone
5655
from decimal import Decimal
5756
from threading import Thread
5857
from time import sleep

0 commit comments

Comments
 (0)