diff --git a/src/MySQLdb/_mysql.c b/src/MySQLdb/_mysql.c index 4463f627..b8a19d26 100644 --- a/src/MySQLdb/_mysql.c +++ b/src/MySQLdb/_mysql.c @@ -1484,6 +1484,26 @@ _mysql_ResultObject_fetch_row( return NULL; } +static const char _mysql_ResultObject_discard__doc__[] = +"discard() -- Discard remaining rows in the resultset."; + +static PyObject * +_mysql_ResultObject_discard( + _mysql_ResultObject *self, + PyObject *noargs) +{ + check_result_connection(self); + + MYSQL_ROW row; + while (NULL != (row = mysql_fetch_row(self->result))) { + // do nothing + } + if (mysql_errno(self->conn)) { + return _mysql_Exception(self->conn); + } + Py_RETURN_NONE; +} + static char _mysql_ConnectionObject_change_user__doc__[] = "Changes the user and causes the database specified by db to\n\ become the default (current) database on the connection\n\ @@ -2081,6 +2101,43 @@ _mysql_ConnectionObject_use_result( return result; } +static const char _mysql_ConnectionObject_discard_result__doc__[] = +"Discard current result set.\n\n" +"This function can be called instead of use_result() or store_result(). Non-standard."; + +static PyObject * +_mysql_ConnectionObject_discard_result( + _mysql_ConnectionObject *self, + PyObject *noargs) +{ + check_connection(self); + MYSQL *conn = &(self->connection); + + Py_BEGIN_ALLOW_THREADS; + + MYSQL_RES *res = mysql_use_result(conn); + if (res == NULL) { + Py_BLOCK_THREADS; + if (mysql_errno(conn) != 0) { + // fprintf(stderr, "mysql_use_result failed: %s\n", mysql_error(conn)); + return _mysql_Exception(self); + } + Py_RETURN_NONE; + } + + MYSQL_ROW row; + while (NULL != (row = mysql_fetch_row(res))) { + // do nothing. + } + mysql_free_result(res); + Py_END_ALLOW_THREADS; + if (mysql_errno(conn)) { + // fprintf(stderr, "mysql_free_result failed: %s\n", mysql_error(conn)); + return _mysql_Exception(self); + } + Py_RETURN_NONE; +} + static void _mysql_ConnectionObject_dealloc( _mysql_ConnectionObject *self) @@ -2376,6 +2433,12 @@ static PyMethodDef _mysql_ConnectionObject_methods[] = { METH_NOARGS, _mysql_ConnectionObject_use_result__doc__ }, + { + "discard_result", + (PyCFunction)_mysql_ConnectionObject_discard_result, + METH_NOARGS, + _mysql_ConnectionObject_discard_result__doc__ + }, {NULL, NULL} /* sentinel */ }; @@ -2437,6 +2500,12 @@ static PyMethodDef _mysql_ResultObject_methods[] = { METH_VARARGS | METH_KEYWORDS, _mysql_ResultObject_fetch_row__doc__ }, + { + "discard", + (PyCFunction)_mysql_ResultObject_discard, + METH_NOARGS, + _mysql_ResultObject_discard__doc__ + }, { "field_flags", (PyCFunction)_mysql_ResultObject_field_flags, diff --git a/src/MySQLdb/cursors.py b/src/MySQLdb/cursors.py index fdf52c0b..d3b2947b 100644 --- a/src/MySQLdb/cursors.py +++ b/src/MySQLdb/cursors.py @@ -75,13 +75,30 @@ def __init__(self, connection): self.rownumber = None self._rows = None + def _discard(self): + self.description = None + self.description_flags = None + self.rowcount = -1 + self.lastrowid = None + self._rows = None + self.rownumber = None + + if self._result: + self._result.discard() + self._result = None + + con = self.connection + if con is None: + return + while con.next_result() == 0: # -1 means no more data. + con.discard_result() + def close(self): """Close the cursor. No further queries will be possible.""" try: if self.connection is None: return - while self.nextset(): - pass + self._discard() finally: self.connection = None self._result = None @@ -180,8 +197,7 @@ def execute(self, query, args=None): Returns integer represents rows affected, if any """ - while self.nextset(): - pass + self._discard() mogrified_query = self._mogrify(query, args) diff --git a/tests/test_cursor.py b/tests/test_cursor.py index c681b63b..5cb98910 100644 --- a/tests/test_cursor.py +++ b/tests/test_cursor.py @@ -1,4 +1,4 @@ -# import pytest +import pytest import MySQLdb.cursors from configdb import connection_factory @@ -186,3 +186,39 @@ def test_mogrify_with_dict_args(): assert mogrified_query == "SELECT 1, 2" assert mogrified_query == cursor._executed.decode() + + +# Test that cursor can be used without reading whole resultset. +@pytest.mark.parametrize("Cursor", [MySQLdb.cursors.Cursor, MySQLdb.cursors.SSCursor]) +def test_cursor_discard_result(Cursor): + conn = connect() + cursor = conn.cursor(Cursor) + + cursor.execute( + """\ +CREATE TABLE test_cursor_discard_result ( + id INTEGER PRIMARY KEY AUTO_INCREMENT, + data VARCHAR(100) +)""" + ) + _tables.append("test_cursor_discard_result") + + cursor.executemany( + "INSERT INTO test_cursor_discard_result (id, data) VALUES (%s, %s)", + [(i, f"row {i}") for i in range(1, 101)], + ) + + cursor.execute( + """\ +SELECT * FROM test_cursor_discard_result WHERE id <= 10; +SELECT * FROM test_cursor_discard_result WHERE id BETWEEN 11 AND 20; +SELECT * FROM test_cursor_discard_result WHERE id BETWEEN 21 AND 30; +""" + ) + cursor.nextset() + assert cursor.fetchone() == (11, "row 11") + + cursor.execute( + "SELECT * FROM test_cursor_discard_result WHERE id BETWEEN 31 AND 40" + ) + assert cursor.fetchone() == (31, "row 31")