Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5bfa062

Browse files
committed
Issue #11688: Add sqlite3.Connection.set_trace_callback(). Patch by Torsten Landschoff.
1 parent d7edf3b commit 5bfa062

4 files changed

Lines changed: 128 additions & 1 deletion

File tree

Doc/library/sqlite3.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,22 @@ Connection Objects
369369
method with :const:`None` for *handler*.
370370

371371

372+
.. method:: Connection.set_trace_callback(trace_callback)
373+
374+
Registers *trace_callback* to be called for each SQL statement that is
375+
actually executed by the SQLite backend.
376+
377+
The only argument passed to the callback is the statement (as string) that
378+
is being executed. The return value of the callback is ignored. Note that
379+
the backend does not only run statements passed to the :meth:`Cursor.execute`
380+
methods. Other sources include the transaction management of the Python
381+
module and the execution of triggers defined in the current database.
382+
383+
Passing :const:`None` as *trace_callback* will disable the trace callback.
384+
385+
.. versionadded:: 3.3
386+
387+
372388
.. method:: Connection.enable_load_extension(enabled)
373389

374390
This routine allows/disallows the SQLite engine to load SQLite extensions

Lib/sqlite3/test/hooks.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,56 @@ def progress():
175175
con.execute("select 1 union select 2 union select 3").fetchall()
176176
self.assertEqual(action, 0, "progress handler was not cleared")
177177

178+
class TraceCallbackTests(unittest.TestCase):
179+
def CheckTraceCallbackUsed(self):
180+
"""
181+
Test that the trace callback is invoked once it is set.
182+
"""
183+
con = sqlite.connect(":memory:")
184+
traced_statements = []
185+
def trace(statement):
186+
traced_statements.append(statement)
187+
con.set_trace_callback(trace)
188+
con.execute("create table foo(a, b)")
189+
self.assertTrue(traced_statements)
190+
self.assertTrue(any("create table foo" in stmt for stmt in traced_statements))
191+
192+
def CheckClearTraceCallback(self):
193+
"""
194+
Test that setting the trace callback to None clears the previously set callback.
195+
"""
196+
con = sqlite.connect(":memory:")
197+
traced_statements = []
198+
def trace(statement):
199+
traced_statements.append(statement)
200+
con.set_trace_callback(trace)
201+
con.set_trace_callback(None)
202+
con.execute("create table foo(a, b)")
203+
self.assertFalse(traced_statements, "trace callback was not cleared")
204+
205+
def CheckUnicodeContent(self):
206+
"""
207+
Test that the statement can contain unicode literals.
208+
"""
209+
unicode_value = '\xf6\xe4\xfc\xd6\xc4\xdc\xdf\u20ac'
210+
con = sqlite.connect(":memory:")
211+
traced_statements = []
212+
def trace(statement):
213+
traced_statements.append(statement)
214+
con.set_trace_callback(trace)
215+
con.execute("create table foo(x)")
216+
con.execute("insert into foo(x) values (?)", (unicode_value,))
217+
con.commit()
218+
self.assertTrue(any(unicode_value in stmt for stmt in traced_statements),
219+
"Unicode data garbled in trace callback")
220+
221+
222+
178223
def suite():
179224
collation_suite = unittest.makeSuite(CollationTests, "Check")
180225
progress_suite = unittest.makeSuite(ProgressTests, "Check")
181-
return unittest.TestSuite((collation_suite, progress_suite))
226+
trace_suite = unittest.makeSuite(TraceCallbackTests, "Check")
227+
return unittest.TestSuite((collation_suite, progress_suite, trace_suite))
182228

183229
def test():
184230
runner = unittest.TextTestRunner()

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ Core and Builtins
8787
Library
8888
-------
8989

90+
- Issue #11688: Add sqlite3.Connection.set_trace_callback(). Patch by
91+
Torsten Landschoff.
92+
9093
- Issue #11746: Fix SSLContext.load_cert_chain() to accept elliptic curve
9194
private keys.
9295

Modules/_sqlite/connection.c

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,38 @@ static int _progress_handler(void* user_arg)
904904
return rc;
905905
}
906906

907+
static void _trace_callback(void* user_arg, const char* statement_string)
908+
{
909+
PyObject *py_statement = NULL;
910+
PyObject *ret = NULL;
911+
912+
#ifdef WITH_THREAD
913+
PyGILState_STATE gilstate;
914+
915+
gilstate = PyGILState_Ensure();
916+
#endif
917+
py_statement = PyUnicode_DecodeUTF8(statement_string,
918+
strlen(statement_string), "replace");
919+
if (py_statement) {
920+
ret = PyObject_CallFunctionObjArgs((PyObject*)user_arg, py_statement, NULL);
921+
Py_DECREF(py_statement);
922+
}
923+
924+
if (ret) {
925+
Py_DECREF(ret);
926+
} else {
927+
if (_enable_callback_tracebacks) {
928+
PyErr_Print();
929+
} else {
930+
PyErr_Clear();
931+
}
932+
}
933+
934+
#ifdef WITH_THREAD
935+
PyGILState_Release(gilstate);
936+
#endif
937+
}
938+
907939
static PyObject* pysqlite_connection_set_authorizer(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
908940
{
909941
PyObject* authorizer_cb;
@@ -963,6 +995,34 @@ static PyObject* pysqlite_connection_set_progress_handler(pysqlite_Connection* s
963995
return Py_None;
964996
}
965997

998+
static PyObject* pysqlite_connection_set_trace_callback(pysqlite_Connection* self, PyObject* args, PyObject* kwargs)
999+
{
1000+
PyObject* trace_callback;
1001+
1002+
static char *kwlist[] = { "trace_callback", NULL };
1003+
1004+
if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) {
1005+
return NULL;
1006+
}
1007+
1008+
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O:set_trace_callback",
1009+
kwlist, &trace_callback)) {
1010+
return NULL;
1011+
}
1012+
1013+
if (trace_callback == Py_None) {
1014+
/* None clears the trace callback previously set */
1015+
sqlite3_trace(self->db, 0, (void*)0);
1016+
} else {
1017+
if (PyDict_SetItem(self->function_pinboard, trace_callback, Py_None) == -1)
1018+
return NULL;
1019+
sqlite3_trace(self->db, _trace_callback, trace_callback);
1020+
}
1021+
1022+
Py_INCREF(Py_None);
1023+
return Py_None;
1024+
}
1025+
9661026
#ifdef HAVE_LOAD_EXTENSION
9671027
static PyObject* pysqlite_enable_load_extension(pysqlite_Connection* self, PyObject* args)
9681028
{
@@ -1516,6 +1576,8 @@ static PyMethodDef connection_methods[] = {
15161576
#endif
15171577
{"set_progress_handler", (PyCFunction)pysqlite_connection_set_progress_handler, METH_VARARGS|METH_KEYWORDS,
15181578
PyDoc_STR("Sets progress handler callback. Non-standard.")},
1579+
{"set_trace_callback", (PyCFunction)pysqlite_connection_set_trace_callback, METH_VARARGS|METH_KEYWORDS,
1580+
PyDoc_STR("Sets a trace callback called for each SQL statement (passed as unicode). Non-standard.")},
15191581
{"execute", (PyCFunction)pysqlite_connection_execute, METH_VARARGS,
15201582
PyDoc_STR("Executes a SQL statement. Non-standard.")},
15211583
{"executemany", (PyCFunction)pysqlite_connection_executemany, METH_VARARGS,

0 commit comments

Comments
 (0)