Skip to content

Commit c79e784

Browse files
committed
bpo-36073: Raise ProgrammingError on recursive usage of cursors in sqlite converters
1 parent cc2ffcd commit c79e784

File tree

3 files changed

+64
-7
lines changed

3 files changed

+64
-7
lines changed

Lib/sqlite3/test/regression.py

+36-1
Original file line numberDiff line numberDiff line change
@@ -415,9 +415,44 @@ def test_return_empty_bytestring(self):
415415
self.assertEqual(val, b'')
416416

417417

418+
class ConverterProgrammingErrorTestCase(unittest.TestCase):
419+
def setUp(self):
420+
self.con = sqlite.connect(':memory:', detect_types=sqlite.PARSE_COLNAMES)
421+
self.cur = self.con.cursor()
422+
self.cur.execute('create table test(x foo)')
423+
424+
sqlite.converters['CURSOR_INIT'] = lambda x: self.cur.__init__(self.con)
425+
sqlite.converters['CURSOR_CLOSE'] = lambda x: self.cur.close()
426+
sqlite.converters['CURSOR_ITER'] = lambda x, l=[]: self.cur.fetchone() if l else l.append(None)
427+
428+
def tearDown(self):
429+
del sqlite.converters['CURSOR_INIT']
430+
del sqlite.converters['CURSOR_CLOSE']
431+
del sqlite.converters['CURSOR_ITER']
432+
self.cur.close()
433+
self.con.close()
434+
435+
def test_cursor_init(self):
436+
self.cur.execute('insert into test(x) values (?)', ('foo',))
437+
with self.assertRaises(sqlite.ProgrammingError):
438+
self.cur.execute('select x as "x [CURSOR_INIT]", x from test')
439+
440+
def test_cursor_close(self):
441+
self.cur.execute('insert into test(x) values (?)', ('foo',))
442+
with self.assertRaises(sqlite.ProgrammingError):
443+
self.cur.execute('select x as "x [CURSOR_CLOSE]", x from test')
444+
445+
def test_cursor_iter(self):
446+
self.cur.executemany('insert into test(x) values (?)', (('foo',),) * 2)
447+
self.cur.execute('select x as "x [CURSOR_ITER]", x from test')
448+
with self.assertRaises(sqlite.ProgrammingError):
449+
self.cur.fetchone()
450+
451+
418452
def suite():
419453
tests = [
420-
RegressionTests
454+
RegressionTests,
455+
ConverterProgrammingErrorTestCase,
421456
]
422457
return unittest.TestSuite(
423458
[unittest.TestLoader().loadTestsFromTestCase(t) for t in tests]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Raise :exc:`~sqlite3.ProgrammingError` on recursive usage of cursors in
2+
:mod:`sqlite3` converters. Patch by Sergey Fedoseev.

Modules/_sqlite/cursor.c

+26-6
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@
2626
#include "util.h"
2727
#include "clinic/cursor.c.h"
2828

29+
static int
30+
check_cursor_locked(pysqlite_Cursor* cur)
31+
{
32+
if (cur->locked) {
33+
PyErr_SetString(
34+
pysqlite_ProgrammingError,
35+
"Recursive use of cursors not allowed."
36+
);
37+
return 0;
38+
}
39+
return 1;
40+
}
41+
2942
/*[clinic input]
3043
module _sqlite3
3144
class _sqlite3.Cursor "pysqlite_Cursor *" "pysqlite_CursorType"
@@ -47,6 +60,10 @@ pysqlite_cursor_init_impl(pysqlite_Cursor *self,
4760
pysqlite_Connection *connection)
4861
/*[clinic end generated code: output=ac59dce49a809ca8 input=a8a4f75ac90999b2]*/
4962
{
63+
if (!check_cursor_locked(self)) {
64+
return -1;
65+
}
66+
5067
Py_INCREF(connection);
5168
Py_XSETREF(self->connection, connection);
5269
Py_CLEAR(self->statement);
@@ -384,12 +401,9 @@ static int check_cursor(pysqlite_Cursor* cur)
384401
return 0;
385402
}
386403

387-
if (cur->locked) {
388-
PyErr_SetString(pysqlite_ProgrammingError, "Recursive use of cursors not allowed.");
389-
return 0;
390-
}
391-
392-
return pysqlite_check_thread(cur->connection) && pysqlite_check_connection(cur->connection);
404+
return check_cursor_locked(cur) &&
405+
pysqlite_check_thread(cur->connection) &&
406+
pysqlite_check_connection(cur->connection);
393407
}
394408

395409
static PyObject *
@@ -811,7 +825,9 @@ pysqlite_cursor_iternext(pysqlite_Cursor *self)
811825
}
812826

813827
if (rc == SQLITE_ROW) {
828+
self->locked = 1;
814829
self->next_row = _pysqlite_fetch_one_row(self);
830+
self->locked = 0;
815831
if (self->next_row == NULL) {
816832
(void)pysqlite_statement_reset(self->statement);
817833
return NULL;
@@ -962,6 +978,10 @@ static PyObject *
962978
pysqlite_cursor_close_impl(pysqlite_Cursor *self)
963979
/*[clinic end generated code: output=b6055e4ec6fe63b6 input=08b36552dbb9a986]*/
964980
{
981+
if (!check_cursor_locked(self)) {
982+
return NULL;
983+
}
984+
965985
if (!self->connection) {
966986
PyErr_SetString(pysqlite_ProgrammingError,
967987
"Base Cursor.__init__ not called.");

0 commit comments

Comments
 (0)