Skip to content

Commit 178d79a

Browse files
[3.10] gh-89301: Fix regression with bound values in traced SQLite statements (#92147)
(cherry picked from commit 721aa96)
1 parent 6712022 commit 178d79a

File tree

3 files changed

+77
-15
lines changed

3 files changed

+77
-15
lines changed

Lib/sqlite3/test/hooks.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
# misrepresented as being the original software.
2121
# 3. This notice may not be removed or altered from any source distribution.
2222

23+
import contextlib
2324
import unittest
2425
import sqlite3 as sqlite
2526

@@ -200,6 +201,16 @@ def progress():
200201
self.assertEqual(action, 0, "progress handler was not cleared")
201202

202203
class TraceCallbackTests(unittest.TestCase):
204+
@contextlib.contextmanager
205+
def check_stmt_trace(self, cx, expected):
206+
try:
207+
traced = []
208+
cx.set_trace_callback(lambda stmt: traced.append(stmt))
209+
yield
210+
finally:
211+
self.assertEqual(traced, expected)
212+
cx.set_trace_callback(None)
213+
203214
def test_trace_callback_used(self):
204215
"""
205216
Test that the trace callback is invoked once it is set.
@@ -261,6 +272,21 @@ def trace(statement):
261272
cur.execute(queries[1])
262273
self.assertEqual(traced_statements, queries)
263274

275+
def test_trace_expanded_sql(self):
276+
expected = [
277+
"create table t(t)",
278+
"BEGIN ",
279+
"insert into t values(0)",
280+
"insert into t values(1)",
281+
"insert into t values(2)",
282+
"COMMIT",
283+
]
284+
cx = sqlite.connect(":memory:")
285+
with self.check_stmt_trace(cx, expected):
286+
with cx:
287+
cx.execute("create table t(t)")
288+
cx.executemany("insert into t values(?)", ((v,) for v in range(3)))
289+
264290

265291
def suite():
266292
tests = [
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a regression in the :mod:`sqlite3` trace callback where bound parameters
2+
were not expanded in the passed statement string. The regression was introduced
3+
in Python 3.10 by :issue:`40318`. Patch by Erlend E. Aasland.

Modules/_sqlite/connection.c

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,40 +1050,73 @@ static int _progress_handler(void* user_arg)
10501050
* may change in future releases. Callback implementations should return zero
10511051
* to ensure future compatibility.
10521052
*/
1053-
static int _trace_callback(unsigned int type, void* user_arg, void* prepared_statement, void* statement_string)
1053+
static int
1054+
_trace_callback(unsigned int type, void *callable, void *stmt, void *sql)
10541055
#else
1055-
static void _trace_callback(void* user_arg, const char* statement_string)
1056+
static void
1057+
_trace_callback(void *callable, const char *sql)
10561058
#endif
10571059
{
1058-
PyObject *py_statement = NULL;
1059-
PyObject *ret = NULL;
1060-
1061-
PyGILState_STATE gilstate;
1062-
10631060
#ifdef HAVE_TRACE_V2
10641061
if (type != SQLITE_TRACE_STMT) {
10651062
return 0;
10661063
}
10671064
#endif
10681065

1069-
gilstate = PyGILState_Ensure();
1070-
py_statement = PyUnicode_DecodeUTF8(statement_string,
1071-
strlen(statement_string), "replace");
1066+
PyGILState_STATE gilstate = PyGILState_Ensure();
1067+
1068+
PyObject *py_statement = NULL;
1069+
#ifdef HAVE_TRACE_V2
1070+
const char *expanded_sql = sqlite3_expanded_sql((sqlite3_stmt *)stmt);
1071+
if (expanded_sql == NULL) {
1072+
sqlite3 *db = sqlite3_db_handle((sqlite3_stmt *)stmt);
1073+
if (sqlite3_errcode(db) == SQLITE_NOMEM) {
1074+
(void)PyErr_NoMemory();
1075+
goto exit;
1076+
}
1077+
1078+
PyErr_SetString(pysqlite_DataError,
1079+
"Expanded SQL string exceeds the maximum string length");
1080+
if (_pysqlite_enable_callback_tracebacks) {
1081+
PyErr_Print();
1082+
} else {
1083+
PyErr_Clear();
1084+
}
1085+
1086+
// Fall back to unexpanded sql
1087+
py_statement = PyUnicode_FromString((const char *)sql);
1088+
}
1089+
else {
1090+
py_statement = PyUnicode_FromString(expanded_sql);
1091+
sqlite3_free((void *)expanded_sql);
1092+
}
1093+
#else
1094+
if (sql == NULL) {
1095+
PyErr_SetString(pysqlite_DataError,
1096+
"Expanded SQL string exceeds the maximum string length");
1097+
if (_pysqlite_enable_callback_tracebacks) {
1098+
PyErr_Print();
1099+
} else {
1100+
PyErr_Clear();
1101+
}
1102+
goto exit;
1103+
}
1104+
py_statement = PyUnicode_FromString(sql);
1105+
#endif
10721106
if (py_statement) {
1073-
ret = PyObject_CallOneArg((PyObject*)user_arg, py_statement);
1107+
PyObject *ret = PyObject_CallOneArg((PyObject *)callable, py_statement);
10741108
Py_DECREF(py_statement);
1109+
Py_XDECREF(ret);
10751110
}
1076-
1077-
if (ret) {
1078-
Py_DECREF(ret);
1079-
} else {
1111+
if (PyErr_Occurred()) {
10801112
if (_pysqlite_enable_callback_tracebacks) {
10811113
PyErr_Print();
10821114
} else {
10831115
PyErr_Clear();
10841116
}
10851117
}
10861118

1119+
exit:
10871120
PyGILState_Release(gilstate);
10881121
#ifdef HAVE_TRACE_V2
10891122
return 0;

0 commit comments

Comments
 (0)