Skip to content

Commit a2e9706

Browse files
authored
Discard results without converting them into Python objects. (#601)
Fixes #560.
1 parent 0220f42 commit a2e9706

File tree

3 files changed

+126
-5
lines changed

3 files changed

+126
-5
lines changed

src/MySQLdb/_mysql.c

+69
Original file line numberDiff line numberDiff line change
@@ -1484,6 +1484,26 @@ _mysql_ResultObject_fetch_row(
14841484
return NULL;
14851485
}
14861486

1487+
static const char _mysql_ResultObject_discard__doc__[] =
1488+
"discard() -- Discard remaining rows in the resultset.";
1489+
1490+
static PyObject *
1491+
_mysql_ResultObject_discard(
1492+
_mysql_ResultObject *self,
1493+
PyObject *noargs)
1494+
{
1495+
check_result_connection(self);
1496+
1497+
MYSQL_ROW row;
1498+
while (NULL != (row = mysql_fetch_row(self->result))) {
1499+
// do nothing
1500+
}
1501+
if (mysql_errno(self->conn)) {
1502+
return _mysql_Exception(self->conn);
1503+
}
1504+
Py_RETURN_NONE;
1505+
}
1506+
14871507
static char _mysql_ConnectionObject_change_user__doc__[] =
14881508
"Changes the user and causes the database specified by db to\n\
14891509
become the default (current) database on the connection\n\
@@ -2081,6 +2101,43 @@ _mysql_ConnectionObject_use_result(
20812101
return result;
20822102
}
20832103

2104+
static const char _mysql_ConnectionObject_discard_result__doc__[] =
2105+
"Discard current result set.\n\n"
2106+
"This function can be called instead of use_result() or store_result(). Non-standard.";
2107+
2108+
static PyObject *
2109+
_mysql_ConnectionObject_discard_result(
2110+
_mysql_ConnectionObject *self,
2111+
PyObject *noargs)
2112+
{
2113+
check_connection(self);
2114+
MYSQL *conn = &(self->connection);
2115+
2116+
Py_BEGIN_ALLOW_THREADS;
2117+
2118+
MYSQL_RES *res = mysql_use_result(conn);
2119+
if (res == NULL) {
2120+
Py_BLOCK_THREADS;
2121+
if (mysql_errno(conn) != 0) {
2122+
// fprintf(stderr, "mysql_use_result failed: %s\n", mysql_error(conn));
2123+
return _mysql_Exception(self);
2124+
}
2125+
Py_RETURN_NONE;
2126+
}
2127+
2128+
MYSQL_ROW row;
2129+
while (NULL != (row = mysql_fetch_row(res))) {
2130+
// do nothing.
2131+
}
2132+
mysql_free_result(res);
2133+
Py_END_ALLOW_THREADS;
2134+
if (mysql_errno(conn)) {
2135+
// fprintf(stderr, "mysql_free_result failed: %s\n", mysql_error(conn));
2136+
return _mysql_Exception(self);
2137+
}
2138+
Py_RETURN_NONE;
2139+
}
2140+
20842141
static void
20852142
_mysql_ConnectionObject_dealloc(
20862143
_mysql_ConnectionObject *self)
@@ -2376,6 +2433,12 @@ static PyMethodDef _mysql_ConnectionObject_methods[] = {
23762433
METH_NOARGS,
23772434
_mysql_ConnectionObject_use_result__doc__
23782435
},
2436+
{
2437+
"discard_result",
2438+
(PyCFunction)_mysql_ConnectionObject_discard_result,
2439+
METH_NOARGS,
2440+
_mysql_ConnectionObject_discard_result__doc__
2441+
},
23792442
{NULL, NULL} /* sentinel */
23802443
};
23812444

@@ -2437,6 +2500,12 @@ static PyMethodDef _mysql_ResultObject_methods[] = {
24372500
METH_VARARGS | METH_KEYWORDS,
24382501
_mysql_ResultObject_fetch_row__doc__
24392502
},
2503+
{
2504+
"discard",
2505+
(PyCFunction)_mysql_ResultObject_discard,
2506+
METH_NOARGS,
2507+
_mysql_ResultObject_discard__doc__
2508+
},
24402509
{
24412510
"field_flags",
24422511
(PyCFunction)_mysql_ResultObject_field_flags,

src/MySQLdb/cursors.py

+20-4
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,30 @@ def __init__(self, connection):
7575
self.rownumber = None
7676
self._rows = None
7777

78+
def _discard(self):
79+
self.description = None
80+
self.description_flags = None
81+
self.rowcount = -1
82+
self.lastrowid = None
83+
self._rows = None
84+
self.rownumber = None
85+
86+
if self._result:
87+
self._result.discard()
88+
self._result = None
89+
90+
con = self.connection
91+
if con is None:
92+
return
93+
while con.next_result() == 0: # -1 means no more data.
94+
con.discard_result()
95+
7896
def close(self):
7997
"""Close the cursor. No further queries will be possible."""
8098
try:
8199
if self.connection is None:
82100
return
83-
while self.nextset():
84-
pass
101+
self._discard()
85102
finally:
86103
self.connection = None
87104
self._result = None
@@ -180,8 +197,7 @@ def execute(self, query, args=None):
180197
181198
Returns integer represents rows affected, if any
182199
"""
183-
while self.nextset():
184-
pass
200+
self._discard()
185201

186202
mogrified_query = self._mogrify(query, args)
187203

tests/test_cursor.py

+37-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# import pytest
1+
import pytest
22
import MySQLdb.cursors
33
from configdb import connection_factory
44

@@ -186,3 +186,39 @@ def test_mogrify_with_dict_args():
186186

187187
assert mogrified_query == "SELECT 1, 2"
188188
assert mogrified_query == cursor._executed.decode()
189+
190+
191+
# Test that cursor can be used without reading whole resultset.
192+
@pytest.mark.parametrize("Cursor", [MySQLdb.cursors.Cursor, MySQLdb.cursors.SSCursor])
193+
def test_cursor_discard_result(Cursor):
194+
conn = connect()
195+
cursor = conn.cursor(Cursor)
196+
197+
cursor.execute(
198+
"""\
199+
CREATE TABLE test_cursor_discard_result (
200+
id INTEGER PRIMARY KEY AUTO_INCREMENT,
201+
data VARCHAR(100)
202+
)"""
203+
)
204+
_tables.append("test_cursor_discard_result")
205+
206+
cursor.executemany(
207+
"INSERT INTO test_cursor_discard_result (id, data) VALUES (%s, %s)",
208+
[(i, f"row {i}") for i in range(1, 101)],
209+
)
210+
211+
cursor.execute(
212+
"""\
213+
SELECT * FROM test_cursor_discard_result WHERE id <= 10;
214+
SELECT * FROM test_cursor_discard_result WHERE id BETWEEN 11 AND 20;
215+
SELECT * FROM test_cursor_discard_result WHERE id BETWEEN 21 AND 30;
216+
"""
217+
)
218+
cursor.nextset()
219+
assert cursor.fetchone() == (11, "row 11")
220+
221+
cursor.execute(
222+
"SELECT * FROM test_cursor_discard_result WHERE id BETWEEN 31 AND 40"
223+
)
224+
assert cursor.fetchone() == (31, "row 31")

0 commit comments

Comments
 (0)