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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy