Skip to content

Commit b0c8b16

Browse files
committed
correctly fix sqlite3's executemany
1 parent 3952cfe commit b0c8b16

File tree

3 files changed

+39
-42
lines changed

3 files changed

+39
-42
lines changed

Lib/test/test_sqlite3/test_dbapi.py

Lines changed: 31 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -733,21 +733,27 @@ class CxWrapper:
733733
def __init__(self, cx):
734734
self.cx = cx
735735

736+
def side_effect(self):
737+
self.cx.close()
736738

737-
class EvilParamsWithIter(CxWrapper):
738739

740+
class ParamsCxCloseInIterMany(CxWrapper):
739741
def __iter__(self):
740-
self.cx.close()
741-
return iter([(1,)])
742+
self.side_effect()
743+
return iter([(1,), (2,), (3,)])
742744

743745

744-
class EvilParamsWithNext(CxWrapper):
746+
class ParamsCxCloseInNext(CxWrapper):
747+
def __init__(self, cx):
748+
super().__init__(cx)
749+
self.r = iter(range(10))
750+
745751
def __iter__(self):
746752
return self
747753

748754
def __next__(self):
749-
self.cx.close()
750-
return (1,)
755+
self.side_effect()
756+
return (next(self.r),)
751757

752758

753759
class CursorTests(unittest.TestCase):
@@ -764,16 +770,6 @@ def tearDown(self):
764770
self.cu.close()
765771
self.cx.close()
766772

767-
def do_test_connection_use_after_close(self, method_name, params_class):
768-
# Prevent SIGSEGV with iterable of parameters closing the connection.
769-
# Regression test for https://github.com/python/cpython/issues/143198.
770-
cx = sqlite.connect(":memory:")
771-
self.addCleanup(cx.close)
772-
cu = cx.cursor()
773-
params = params_class(cx)
774-
method = operator.methodcaller(method_name, "SELECT ?", params)
775-
self.assertRaises(sqlite.ProgrammingError, method, cu)
776-
777773
def test_execute_no_args(self):
778774
self.cu.execute("delete from test")
779775

@@ -845,10 +841,6 @@ def test_execute_non_iterable(self):
845841
self.cu.execute("insert into test(id) values (?)", 42)
846842
self.assertEqual(str(cm.exception), 'parameters are of unsupported type')
847843

848-
@subTests("params_class", (EvilParamsWithIter, EvilParamsWithNext))
849-
def test_execute_use_after_close(self, params_class):
850-
self.do_test_connection_use_after_close("execute", params_class)
851-
852844
def test_execute_wrong_no_of_args1(self):
853845
# too many parameters
854846
with self.assertRaises(sqlite.ProgrammingError):
@@ -1066,9 +1058,17 @@ def test_execute_many_not_iterable(self):
10661058
with self.assertRaises(TypeError):
10671059
self.cu.executemany("insert into test(income) values (?)", 42)
10681060

1069-
@subTests("params_class", (EvilParamsWithIter, EvilParamsWithNext))
1061+
@subTests("params_class", (ParamsCxCloseInIterMany, ParamsCxCloseInNext))
10701062
def test_executemany_use_after_close(self, params_class):
1071-
self.do_test_connection_use_after_close("executemany", params_class)
1063+
# Prevent SIGSEGV with iterable of parameters closing the connection.
1064+
# Regression test for https://github.com/python/cpython/issues/143198.
1065+
cx = sqlite.connect(":memory:")
1066+
cx.execute("create table tmp(a number)")
1067+
self.addCleanup(cx.close)
1068+
cu = cx.cursor()
1069+
msg = r"Cannot operate on a closed database\."
1070+
with self.assertRaisesRegex(sqlite.ProgrammingError, msg):
1071+
cu.executemany("insert into tmp(a) values (?)", params_class(cx))
10721072

10731073
def test_fetch_iter(self):
10741074
# Optional DB-API extension.
@@ -1685,15 +1685,6 @@ def tearDown(self):
16851685
self.cur.close()
16861686
self.con.close()
16871687

1688-
def do_test_connection_use_after_close(self, method_name, params_class):
1689-
# Prevent SIGSEGV with iterable of parameters closing the connection.
1690-
# Regression test for https://github.com/python/cpython/issues/143198.
1691-
cx = sqlite.connect(":memory:")
1692-
self.addCleanup(cx.close)
1693-
params = params_class(cx)
1694-
method = operator.methodcaller(method_name, "SELECT ?", params)
1695-
self.assertRaises(sqlite.ProgrammingError, method, cx)
1696-
16971688
def test_script_string_sql(self):
16981689
cur = self.cur
16991690
cur.executescript("""
@@ -1760,10 +1751,6 @@ def test_connection_execute(self):
17601751
result = self.con.execute("select 5").fetchone()[0]
17611752
self.assertEqual(result, 5, "Basic test of Connection.execute")
17621753

1763-
@subTests("params_class", (EvilParamsWithIter, EvilParamsWithNext))
1764-
def test_connection_execute_use_after_close(self, params_class):
1765-
self.do_test_connection_use_after_close("execute", params_class)
1766-
17671754
def test_connection_executemany(self):
17681755
con = self.con
17691756
con.execute("create table test(foo)")
@@ -1772,9 +1759,16 @@ def test_connection_executemany(self):
17721759
self.assertEqual(result[0][0], 3, "Basic test of Connection.executemany")
17731760
self.assertEqual(result[1][0], 4, "Basic test of Connection.executemany")
17741761

1775-
@subTests("params_class", (EvilParamsWithIter, EvilParamsWithNext))
1762+
@subTests("params_class", (ParamsCxCloseInIterMany, ParamsCxCloseInNext))
17761763
def test_connection_executemany_use_after_close(self, params_class):
1777-
self.do_test_connection_use_after_close("executemany", params_class)
1764+
# Prevent SIGSEGV with iterable of parameters closing the connection.
1765+
# Regression test for https://github.com/python/cpython/issues/143198.
1766+
cx = sqlite.connect(":memory:")
1767+
cx.execute("create table tmp(a number)")
1768+
self.addCleanup(cx.close)
1769+
msg = r"Cannot operate on a closed database\."
1770+
with self.assertRaisesRegex(sqlite.ProgrammingError, msg):
1771+
cx.executemany("insert into tmp(a) values (?)", params_class(cx))
17781772

17791773
def test_connection_executescript(self):
17801774
con = self.con
Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
:mod:`sqlite3`: fix crashes in :meth:`Connection.execute <sqlite3.Connection.execute>`
2-
and :meth:`Connection.executemany <sqlite3.Connection.executemany>` when iterating over
3-
the query's parameters closes the current connection. A similar issue has been fixed
4-
for the corresponding methods on :class:`~sqlite3.Cursor` objects.
5-
Patch by Bénédikt Tran.
1+
:mod:`sqlite3`: fix crashes in :meth:`Connection.executemany <sqlite3.Connection.executemany>`
2+
and :meth:`Cursor.executemany <sqlite3.Cursor.executemany>` when iterating over
3+
the query's parameters closes the current connection. Patch by Bénédikt Tran.

Modules/_sqlite/cursor.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,11 @@ _pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation
931931
if (!parameters) {
932932
break;
933933
}
934+
// PyIter_Next() may have a side-effect on the connection's state.
935+
// See: https://github.com/python/cpython/issues/143198.
936+
if (!pysqlite_check_connection(self->connection)) {
937+
goto error;
938+
}
934939

935940
bind_parameters(state, self->statement, parameters);
936941
if (PyErr_Occurred()) {

0 commit comments

Comments
 (0)