Repository: jazzband/django-debug-toolbar

Issue:
SQL 'Select/Explain' crashes because of redundant params substitution (fix provided)
When I click on _Select_ or _Explain_ SQL panel I get following error:

``` python
  File "/usr/lib/python2.6/dist-packages/django/core/handlers/base.py", line 115, in get_response
    response = callback(request, *callback_args, **callback_kwargs)
  File "/usr/lib/python2.6/dist-packages/django/views/decorators/csrf.py", line 77, in wrapped_view
    return view_func(*args, **kwargs)
  File "/www/redsys/core/debug_toolbar/views.py", line 35, in sql_select
    cursor.execute(sql, params)
  File "/usr/lib/pymodules/python2.6/redsys/core/debug_toolbar/utils/tracking/db.py", line 145, in execute
    self.cursor, sql, self._quote_params(params)),
  File "/usr/lib/python2.6/dist-packages/django/db/backends/mysql/base.py", line 243, in last_executed_query
    return cursor._last_executed.decode('utf-8')
  File "/usr/lib/python2.6/dist-packages/django/db/backends/util.py", line 29, in __getattr__
    return getattr(self.cursor, attr)
  File "/usr/lib/python2.6/dist-packages/django/db/backends/mysql/base.py", line 150, in __getattr__
    return getattr(self.cursor, attr)
AttributeError: 'Cursor' object has no attribute '_last_executed'
 {middleware.py:process_exception():42}
```

After a bit of debugging I found that DebugToolbar is trying to substitute params to _Select_ / _Explain_ query - but this is wrong - parameters are already substituted because query is read from `form.cleaned_data`.

Fix:
- See  `debug_toolbar/views.py`. 
- Replace `params = form.cleaned_data['params']` with `params = ()` in `sql_select()`, `sql_explain()` and probably even in `sql_profile()` (haven't tested)



Discussion:
The main issue is that the Form uses sql instead of raw_sql + everything which follows on that. The error I get is:

```
Traceback (most recent call last):
  File "/home/florian/.virtualenvs/inyoka/lib/python2.7/site-packages/django/core/handlers/wsgi.py", line 255, in __call__
    response = self.get_response(request)
  File "/home/florian/.virtualenvs/inyoka/lib/python2.7/site-packages/django/core/handlers/base.py", line 178, in get_response
    response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
  File "/home/florian/.virtualenvs/inyoka/lib/python2.7/site-packages/django/core/handlers/base.py", line 115, in get_response
    response = callback(request, *callback_args, **callback_kwargs)
  File "/home/florian/.virtualenvs/inyoka/lib/python2.7/site-packages/django/views/decorators/csrf.py", line 77, in wrapped_view
    return view_func(*args, **kwargs)
  File "/home/florian/sources/django-debug-toolbar/debug_toolbar/views.py", line 26, in sql_select
    cursor.execute(sql, params)
  File "/home/florian/sources/django-debug-toolbar/debug_toolbar/utils/tracking/db.py", line 98, in execute
    return self.cursor.execute(sql, params)
  File "/home/florian/.virtualenvs/inyoka/lib/python2.7/site-packages/django/db/backends/util.py", line 41, in execute
    return self.cursor.execute(sql, params)
  File "/home/florian/.virtualenvs/inyoka/lib/python2.7/site-packages/django/db/backends/mysql/base.py", line 120, in execute
    return self.cursor.execute(query, args)
  File "/home/florian/.virtualenvs/inyoka/lib/python2.7/site-packages/MySQLdb/cursors.py", line 183, in execute
    query = query % db.literal(args)
TypeError: not all arguments converted during string formatting
```

which is also a result of not interpolating quries properly.


Resolving patch:
diff --git a/debug_toolbar/forms.py b/debug_toolbar/forms.py
--- a/debug_toolbar/forms.py
+++ b/debug_toolbar/forms.py
@@ -17,12 +17,14 @@ class SQLSelectForm(forms.Form):
     """
     Validate params
 
-        sql: urlencoded sql with positional arguments
+        sql: The sql statement with interpolated params
+        raw_sql: The sql statement with placeholders
         params: JSON encoded parameter values
         duration: time for SQL to execute passed in from toolbar just for redisplay
         hash: the hash of (secret + sql + params) for tamper checking
     """
     sql = forms.CharField()
+    raw_sql = forms.CharField()
     params = forms.CharField()
     alias = forms.CharField(required=False, initial='default')
     duration = forms.FloatField()
@@ -39,8 +41,8 @@ def __init__(self, *args, **kwargs):
         for name in self.fields:
             self.fields[name].widget = forms.HiddenInput()
 
-    def clean_sql(self):
-        value = self.cleaned_data['sql']
+    def clean_raw_sql(self):
+        value = self.cleaned_data['raw_sql']
 
         if not value.lower().strip().startswith('select'):
             raise ValidationError("Only 'select' queries are allowed.")
@@ -72,8 +74,7 @@ def clean_hash(self):
         return hash
 
     def reformat_sql(self):
-        sql, params = self.cleaned_data['sql'], self.cleaned_data['params']
-        return reformat_sql(self.cursor.db.ops.last_executed_query(self.cursor, sql, params))
+        return reformat_sql(self.cleaned_data['sql'])
 
     def make_hash(self, data):
         params = force_text(settings.SECRET_KEY) + data['sql'] + data['params']
diff --git a/debug_toolbar/views.py b/debug_toolbar/views.py
--- a/debug_toolbar/views.py
+++ b/debug_toolbar/views.py
@@ -19,7 +19,7 @@ def sql_select(request):
     form = SQLSelectForm(request.POST or None)
 
     if form.is_valid():
-        sql = form.cleaned_data['sql']
+        sql = form.cleaned_data['raw_sql']
         params = form.cleaned_data['params']
         cursor = form.cursor
         cursor.execute(sql, params)
@@ -43,7 +43,7 @@ def sql_explain(request):
     form = SQLSelectForm(request.POST or None)
 
     if form.is_valid():
-        sql = form.cleaned_data['sql']
+        sql = form.cleaned_data['raw_sql']
         params = form.cleaned_data['params']
         cursor = form.cursor
 
@@ -80,7 +80,7 @@ def sql_profile(request):
     form = SQLSelectForm(request.POST or None)
 
     if form.is_valid():
-        sql = form.cleaned_data['sql']
+        sql = form.cleaned_data['raw_sql']
         params = form.cleaned_data['params']
         cursor = form.cursor
         result = None


Test patch: