diff --git a/debug_toolbar/panels/sql/tracking.py b/debug_toolbar/panels/sql/tracking.py index f526669c2..c9b84cb30 100644 --- a/debug_toolbar/panels/sql/tracking.py +++ b/debug_toolbar/panels/sql/tracking.py @@ -6,7 +6,7 @@ from time import time from django.utils import six -from django.utils.encoding import force_text +from django.utils.encoding import DjangoUnicodeDecodeError, force_text from debug_toolbar import settings as dt_settings from debug_toolbar.utils import get_stack, get_template_info, tidy_stacktrace @@ -88,7 +88,10 @@ def __init__(self, cursor, db, logger): def _quote_expr(self, element): if isinstance(element, six.string_types): - return "'%s'" % force_text(element).replace("'", "''") + try: + return "'%s'" % force_text(element).replace("'", "''") + except DjangoUnicodeDecodeError: + return repr(element) else: return repr(element) diff --git a/tests/panels/test_sql.py b/tests/panels/test_sql.py index fd8ec2625..4092cfbc1 100644 --- a/tests/panels/test_sql.py +++ b/tests/panels/test_sql.py @@ -113,6 +113,25 @@ def test_param_conversion(self): ('["Foo", true, false]', "[10, 1]", '["2017-12-22 16:07:01"]'), ) + @unittest.skipIf( + connection.vendor in ("sqlite", "postgresql"), + "Mixing bytestrings and text is not allowed on PostgreSQL and SQLite", + ) + def test_binary_param_force_text(self): + self.assertEqual(len(self.panel._queries), 0) + + with connection.cursor() as cursor: + cursor.execute("SELECT * FROM auth_user WHERE username = %s", [b"\xff"]) + + self.panel.process_response(self.request, self.response) + self.panel.generate_stats(self.request, self.response) + + self.assertEqual(len(self.panel._queries), 1) + self.assertEqual( + self.panel._queries[0][1]["sql"], + "SELECT * FROM auth_user WHERE username = '\ufffd'", + ) + @unittest.skipUnless(connection.vendor != "sqlite", "Test invalid for SQLite") def test_raw_query_param_conversion(self): self.assertEqual(len(self.panel._queries), 0)