Skip to content

Commit af0628e

Browse files
Issue python#27881: Fixed possible bugs when setting sqlite3.Connection.isolation_level.
Based on patch by Xiang Zhang.
2 parents 5c071c1 + 2891492 commit af0628e

4 files changed

Lines changed: 65 additions & 46 deletions

File tree

Lib/sqlite3/test/regression.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,34 @@ def CheckRegisterAdapter(self):
146146
self.assertRaises(TypeError, sqlite.register_adapter, {}, None)
147147

148148
def CheckSetIsolationLevel(self):
149-
"""
150-
See issue 3312.
151-
"""
149+
# See issue 27881.
150+
class CustomStr(str):
151+
def upper(self):
152+
return None
153+
def __del__(self):
154+
con.isolation_level = ""
155+
152156
con = sqlite.connect(":memory:")
153-
setattr(con, "isolation_level", "\xe9")
157+
con.isolation_level = None
158+
for level in "", "DEFERRED", "IMMEDIATE", "EXCLUSIVE":
159+
with self.subTest(level=level):
160+
con.isolation_level = level
161+
con.isolation_level = level.lower()
162+
con.isolation_level = level.capitalize()
163+
con.isolation_level = CustomStr(level)
164+
165+
# setting isolation_level failure should not alter previous state
166+
con.isolation_level = None
167+
con.isolation_level = "DEFERRED"
168+
pairs = [
169+
(1, TypeError), (b'', TypeError), ("abc", ValueError),
170+
("IMMEDIATE\0EXCLUSIVE", ValueError), ("\xe9", ValueError),
171+
]
172+
for value, exc in pairs:
173+
with self.subTest(level=value):
174+
with self.assertRaises(exc):
175+
con.isolation_level = value
176+
self.assertEqual(con.isolation_level, "DEFERRED")
154177

155178
def CheckCursorConstructorCallCheck(self):
156179
"""

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ Library
7070
- Issue #27842: The csv.DictReader now returns rows of type OrderedDict.
7171
(Contributed by Steve Holden.)
7272

73+
- Issue #27881: Fixed possible bugs when setting sqlite3.Connection.isolation_level.
74+
Based on patch by Xiang Zhang.
75+
7376
- Issue #27861: Fixed a crash in sqlite3.Connection.cursor() when a factory
7477
creates not a cursor. Patch by Xiang Zhang.
7578

Modules/_sqlite/connection.c

Lines changed: 33 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@
4343

4444
_Py_IDENTIFIER(cursor);
4545

46+
static const char * const begin_statements[] = {
47+
"BEGIN ",
48+
"BEGIN DEFERRED",
49+
"BEGIN IMMEDIATE",
50+
"BEGIN EXCLUSIVE",
51+
NULL
52+
};
53+
4654
static int pysqlite_connection_set_isolation_level(pysqlite_Connection* self, PyObject* isolation_level);
4755
static void _pysqlite_drop_unused_cursor_references(pysqlite_Connection* self);
4856

@@ -258,9 +266,6 @@ void pysqlite_connection_dealloc(pysqlite_Connection* self)
258266
Py_END_ALLOW_THREADS
259267
}
260268

261-
if (self->begin_statement) {
262-
PyMem_Free(self->begin_statement);
263-
}
264269
Py_XDECREF(self->isolation_level);
265270
Py_XDECREF(self->function_pinboard);
266271
Py_XDECREF(self->row_factory);
@@ -1175,59 +1180,48 @@ static PyObject* pysqlite_connection_get_total_changes(pysqlite_Connection* self
11751180

11761181
static int pysqlite_connection_set_isolation_level(pysqlite_Connection* self, PyObject* isolation_level)
11771182
{
1178-
PyObject* res;
1179-
PyObject* begin_statement;
1180-
static PyObject* begin_word;
1181-
1182-
Py_XDECREF(self->isolation_level);
1183-
1184-
if (self->begin_statement) {
1185-
PyMem_Free(self->begin_statement);
1186-
self->begin_statement = NULL;
1187-
}
1188-
11891183
if (isolation_level == Py_None) {
1190-
Py_INCREF(Py_None);
1191-
self->isolation_level = Py_None;
1192-
1193-
res = pysqlite_connection_commit(self, NULL);
1184+
PyObject *res = pysqlite_connection_commit(self, NULL);
11941185
if (!res) {
11951186
return -1;
11961187
}
11971188
Py_DECREF(res);
11981189

1190+
self->begin_statement = NULL;
11991191
self->inTransaction = 0;
12001192
} else {
1201-
const char *statement;
1202-
Py_ssize_t size;
1203-
1204-
Py_INCREF(isolation_level);
1205-
self->isolation_level = isolation_level;
1206-
1207-
if (!begin_word) {
1208-
begin_word = PyUnicode_FromString("BEGIN ");
1209-
if (!begin_word) return -1;
1210-
}
1211-
begin_statement = PyUnicode_Concat(begin_word, isolation_level);
1212-
if (!begin_statement) {
1193+
const char * const *candidate;
1194+
PyObject *uppercase_level;
1195+
_Py_IDENTIFIER(upper);
1196+
1197+
if (!PyUnicode_Check(isolation_level)) {
1198+
PyErr_Format(PyExc_TypeError,
1199+
"isolation_level must be a string or None, not %.100s",
1200+
Py_TYPE(isolation_level)->tp_name);
12131201
return -1;
12141202
}
12151203

1216-
statement = _PyUnicode_AsStringAndSize(begin_statement, &size);
1217-
if (!statement) {
1218-
Py_DECREF(begin_statement);
1204+
uppercase_level = _PyObject_CallMethodIdObjArgs(
1205+
(PyObject *)&PyUnicode_Type, &PyId_upper,
1206+
isolation_level, NULL);
1207+
if (!uppercase_level) {
12191208
return -1;
12201209
}
1221-
self->begin_statement = PyMem_Malloc(size + 2);
1222-
if (!self->begin_statement) {
1223-
Py_DECREF(begin_statement);
1210+
for (candidate = begin_statements; *candidate; candidate++) {
1211+
if (!PyUnicode_CompareWithASCIIString(uppercase_level, *candidate + 6))
1212+
break;
1213+
}
1214+
Py_DECREF(uppercase_level);
1215+
if (!*candidate) {
1216+
PyErr_SetString(PyExc_ValueError,
1217+
"invalid value for isolation_level");
12241218
return -1;
12251219
}
1226-
1227-
strcpy(self->begin_statement, statement);
1228-
Py_DECREF(begin_statement);
1220+
self->begin_statement = *candidate;
12291221
}
12301222

1223+
Py_INCREF(isolation_level);
1224+
Py_XSETREF(self->isolation_level, isolation_level);
12311225
return 0;
12321226
}
12331227

Modules/_sqlite/connection.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,8 @@ typedef struct
5555
/* None for autocommit, otherwise a PyUnicode with the isolation level */
5656
PyObject* isolation_level;
5757

58-
/* NULL for autocommit, otherwise a string with the BEGIN statement; will be
59-
* freed in connection destructor */
60-
char* begin_statement;
58+
/* NULL for autocommit, otherwise a string with the BEGIN statement */
59+
const char* begin_statement;
6160

6261
/* 1 if a check should be performed for each API call if the connection is
6362
* used from the same thread it was created in */

0 commit comments

Comments
 (0)