Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Doc/library/sqlite3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1611,6 +1611,9 @@ Cursor objects
If the *size* parameter is used, then it is best for it to retain the same
value from one :meth:`fetchmany` call to the next.

.. versionchanged:: next
Negative *size* values are rejected by raising :exc:`ValueError`.

.. method:: fetchall()

Return all (remaining) rows of a query result as a :class:`list`.
Expand Down Expand Up @@ -1638,6 +1641,9 @@ Cursor objects
Read/write attribute that controls the number of rows returned by :meth:`fetchmany`.
The default value is 1 which means a single row would be fetched per call.

.. versionchanged:: next
Negative values are rejected by raising :exc:`ValueError`.

.. attribute:: connection

Read-only attribute that provides the SQLite database :class:`Connection`
Expand Down
40 changes: 39 additions & 1 deletion Lib/test/test_sqlite3/test_dbapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
# 3. This notice may not be removed or altered from any source distribution.

import contextlib
import functools
import os
import sqlite3 as sqlite
import subprocess
Expand Down Expand Up @@ -1060,7 +1061,7 @@ def test_array_size(self):
# now set to 2
self.cu.arraysize = 2

# now make the query return 3 rows
# now make the query return 2 rows from a table of 3 rows
self.cu.execute("delete from test")
self.cu.execute("insert into test(name) values ('A')")
self.cu.execute("insert into test(name) values ('B')")
Expand All @@ -1070,13 +1071,50 @@ def test_array_size(self):

self.assertEqual(len(res), 2)

def test_invalid_array_size(self):
UINT32_MAX = (1 << 32) - 1
setter = functools.partial(setattr, self.cu, 'arraysize')

self.assertRaises(TypeError, setter, 1.0)
self.assertRaises(ValueError, setter, -3)
self.assertRaises(OverflowError, setter, UINT32_MAX + 1)

def test_fetchmany(self):
# no active SQL statement
res = self.cu.fetchmany()
self.assertEqual(res, [])
res = self.cu.fetchmany(1000)
self.assertEqual(res, [])

# test default parameter
self.cu.execute("select name from test")
res = self.cu.fetchmany()
self.assertEqual(len(res), 1)

# test when the number of requested rows exceeds the actual count
self.cu.execute("select name from test")
res = self.cu.fetchmany(100)
self.assertEqual(len(res), 1)
res = self.cu.fetchmany(100)
self.assertEqual(res, [])

# test when size = 0
self.cu.execute("select name from test")
res = self.cu.fetchmany(0)
self.assertEqual(res, [])
res = self.cu.fetchmany(100)
self.assertEqual(len(res), 1)
res = self.cu.fetchmany(100)
self.assertEqual(res, [])

def test_invalid_fetchmany(self):
UINT32_MAX = (1 << 32) - 1
fetchmany = self.cu.fetchmany

self.assertRaises(TypeError, fetchmany, 1.0)
self.assertRaises(ValueError, fetchmany, -3)
self.assertRaises(OverflowError, fetchmany, UINT32_MAX + 1)

def test_fetchmany_kw_arg(self):
"""Checks if fetchmany works with keyword arguments"""
self.cu.execute("select name from test")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
:mod:`sqlite3`: correctly handle maximum number of rows to fetch in
:meth:`Cursor.fetchmany <sqlite3.Cursor.fetchmany>` and reject negative
values for :attr:`Cursor.arraysize <sqlite3.Cursor.arraysize>`. Patch by
Bénédikt Tran.
52 changes: 47 additions & 5 deletions Modules/_sqlite/clinic/cursor.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 37 additions & 12 deletions Modules/_sqlite/cursor.c
Original file line number Diff line number Diff line change
Expand Up @@ -1159,35 +1159,31 @@ pysqlite_cursor_fetchone_impl(pysqlite_Cursor *self)
/*[clinic input]
_sqlite3.Cursor.fetchmany as pysqlite_cursor_fetchmany
size as maxrows: int(c_default='((pysqlite_Cursor *)self)->arraysize') = 1
size as maxrows: uint32(c_default='((pysqlite_Cursor *)self)->arraysize') = 1
The default value is set by the Cursor.arraysize attribute.
Fetches several rows from the resultset.
[clinic start generated code]*/

static PyObject *
pysqlite_cursor_fetchmany_impl(pysqlite_Cursor *self, int maxrows)
/*[clinic end generated code: output=a8ef31fea64d0906 input=035dbe44a1005bf2]*/
pysqlite_cursor_fetchmany_impl(pysqlite_Cursor *self, uint32_t maxrows)
/*[clinic end generated code: output=3325f2b477c71baf input=a509c412aa70b27e]*/
{
PyObject* row;
PyObject* list;
int counter = 0;

list = PyList_New(0);
if (!list) {
return NULL;
}

while ((row = pysqlite_cursor_iternext((PyObject *)self))) {
if (PyList_Append(list, row) < 0) {
Py_DECREF(row);
break;
}
while (maxrows > 0 && (row = pysqlite_cursor_iternext((PyObject *)self))) {
int rc = PyList_Append(list, row);
Py_DECREF(row);

if (++counter == maxrows) {
if (rc < 0) {
break;
}
maxrows--;
}

if (PyErr_Occurred()) {
Expand Down Expand Up @@ -1301,6 +1297,30 @@ pysqlite_cursor_close_impl(pysqlite_Cursor *self)
Py_RETURN_NONE;
}

/*[clinic input]
@getter
_sqlite3.Cursor.arraysize
[clinic start generated code]*/

static PyObject *
_sqlite3_Cursor_arraysize_get_impl(pysqlite_Cursor *self)
/*[clinic end generated code: output=e0919d97175e6c50 input=3278f8d3ecbd90e3]*/
{
return PyLong_FromUInt32(self->arraysize);
}

/*[clinic input]
@setter
_sqlite3.Cursor.arraysize
[clinic start generated code]*/

static int
_sqlite3_Cursor_arraysize_set_impl(pysqlite_Cursor *self, PyObject *value)
/*[clinic end generated code: output=af59a6b09f8cce6e input=ace48cb114e26060]*/
{
return PyLong_AsUInt32(value, &self->arraysize);
}

static PyMethodDef cursor_methods[] = {
PYSQLITE_CURSOR_CLOSE_METHODDEF
PYSQLITE_CURSOR_EXECUTEMANY_METHODDEF
Expand All @@ -1318,14 +1338,18 @@ static struct PyMemberDef cursor_members[] =
{
{"connection", _Py_T_OBJECT, offsetof(pysqlite_Cursor, connection), Py_READONLY},
{"description", _Py_T_OBJECT, offsetof(pysqlite_Cursor, description), Py_READONLY},
{"arraysize", Py_T_INT, offsetof(pysqlite_Cursor, arraysize), 0},
{"lastrowid", _Py_T_OBJECT, offsetof(pysqlite_Cursor, lastrowid), Py_READONLY},
{"rowcount", Py_T_LONG, offsetof(pysqlite_Cursor, rowcount), Py_READONLY},
{"row_factory", _Py_T_OBJECT, offsetof(pysqlite_Cursor, row_factory), 0},
{"__weaklistoffset__", Py_T_PYSSIZET, offsetof(pysqlite_Cursor, in_weakreflist), Py_READONLY},
{NULL}
};

static struct PyGetSetDef cursor_getsets[] = {
_SQLITE3_CURSOR_ARRAYSIZE_GETSETDEF
{NULL},
};

static const char cursor_doc[] =
PyDoc_STR("SQLite database cursor class.");

Expand All @@ -1336,6 +1360,7 @@ static PyType_Slot cursor_slots[] = {
{Py_tp_iternext, pysqlite_cursor_iternext},
{Py_tp_methods, cursor_methods},
{Py_tp_members, cursor_members},
{Py_tp_getset, cursor_getsets},
{Py_tp_init, pysqlite_cursor_init},
{Py_tp_traverse, cursor_traverse},
{Py_tp_clear, cursor_clear},
Expand Down
2 changes: 1 addition & 1 deletion Modules/_sqlite/cursor.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ typedef struct
pysqlite_Connection* connection;
PyObject* description;
PyObject* row_cast_map;
int arraysize;
uint32_t arraysize;
PyObject* lastrowid;
long rowcount;
PyObject* row_factory;
Expand Down
Loading