Browse files

head back to where i was thinking about, add in a method that just ev…

…als on a text area to see some of the issues, add in screenshot

added in pygments support for syntax highlighting

found out that things like count() don't work to well

still got to build a kick ass interface to replace that text area using the awesome work at djangoski
  • Loading branch information...
1 parent ecc5529 commit 0c5615ec1316214bc1896a7a3ddde7585547e9df Andy McKay committed Mar 9, 2010
View
BIN docs/test.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
15 query_analyzer/forms.py
@@ -0,0 +1,15 @@
+from query_analyzer.shortcuts import list_models
+
+from django import forms
+#from django.contrib.admin.helpers import AdminForm
+from django.conf import settings
+
+class PythonForm(forms.Form):
+ model = forms.ChoiceField(choices=list_models(), label="Choose a model")
+ python = forms.CharField(label="Enter some python", required=False,
+ widget=forms.Textarea(attrs={"rows":"2"}))
+
+def FieldsForm(model):
+ form = type("FieldsForm", (forms.ModelForm,), dict(Meta=type("Meta", (object,), dict(model=model))))
+ return form()
+
View
168 query_analyzer/shortcuts.py
@@ -0,0 +1,168 @@
+from django.conf import settings
+from django.db import connections
+from django.db.backends.sqlite3.base import DatabaseWrapper as sqlite3
+from django.db.models import get_model as django_get_model
+from django.db.models import get_models as django_get_models
+from django.db.models.fields.related import ForeignKey
+
+from itertools import chain
+from query_analyzer.decorators import json_view
+
+try:
+ from pygments import highlight
+ from pygments.lexers import PythonLexer, SqlLexer
+ from pygments.formatters import HtmlFormatter
+ has_pygments = True
+except ImportError:
+ has_pygments = False
+
+class InvalidSQLError(Exception):
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
+
+class Analyzer(object):
+ def explain(self, connection):
+ cursor = connection.cursor()
+ explain_sql = "EXPLAIN %sPLAN %s" % (isinstance(connection, sqlite3) and "QUERY " or "", self.sql)
+ try:
+ cursor.execute(explain_sql, self.params)
+ explain_headers = [d[0] for d in cursor.description]
+ explain_result = cursor.fetchall()
+ sql = connection.ops.last_executed_query(cursor, self.sql, self.params),
+ finally:
+ cursor.close()
+ return explain_headers, explain_result, sql
+
+class AnalyzeSQL(Analyzer):
+ def __init__(self, sql, params):
+ self.sql = sql
+ self.params = params
+
+ def __call__(self):
+ if not self.sql:
+ context = {}
+ else:
+ if self.sql.lower().strip().startswith('select'):
+ if self.params:
+ self.params = simplejson.loads(self.params)
+ else:
+ self.params = {}
+
+ connection = connections['default']
+ cursor = connection.cursor()
+ try:
+ cursor.execute(sql, params)
+ select_headers = [d[0] for d in cursor.description]
+ select_result = cursor.fetchall()
+ finally:
+ cursor.close()
+
+ explain_headers, explain_result, sql_result = _explain(connection)
+ # not sure what to do with sql_result in this case...
+ context = {
+ 'select_result': select_result,
+ 'explain_result': explain_result,
+ 'sql': sql,
+ 'duration': request.GET.get('duration', 0.0),
+ 'select_headers': select_headers,
+ 'explain_headers': explain_headers,
+ }
+ else:
+ raise InvalidSQLError("Only 'select' queries are allowed.")
+
+class AnalyzePython(Analyzer):
+ def __init__(self, model, python):
+ self.model = model
+ self.manager = model.objects
+ self.nice_python = self.python = "%s.objects.%s" % (model.__name__, python.strip())
+ if has_pygments:
+ self.nice_python = highlight(self.python, PythonLexer(), HtmlFormatter())
+ try:
+ self.queryset = eval(self.python, dict_models())
+ except:
+ raise SyntaxError
+
+ def __call__(self):
+ connection = connections[self.queryset.db]
+
+ self.sql, self.params = self.queryset._as_sql(connection)
+ headers, result, sql = self.explain(connection)
+
+ nice_sql = "\n".join(sql)
+ if has_pygments:
+ nice_sql = highlight(nice_sql, SqlLexer(), HtmlFormatter())
+
+ context = {
+ 'select_result': self.queryset.values_list(),
+ 'explain_result': result,
+ 'sql': sql,
+ 'nice_sql': nice_sql,
+ 'nice_python': self.nice_python,
+ 'duration': settings.DEBUG and connection.queries[-1]['time'] or 0.0,
+ 'select_headers': [f.name for f in self.model._meta.fields], #maybe local_fields is better.
+ 'explain_headers': headers,
+ }
+ return context
+
+
+class AnalyzeQueryset(Analyzer):
+ def __init__(self, model, manager, filters, excludes):
+ self.model = model
+ self.manager = manager
+ self.filters = filters
+ self.excludes = excludes
+ self.sql = None
+ self.params = None
+
+ def __call__(self):
+ queryset = manager.all()
+
+ for filter in filters:
+ queryset = queryset.filter(**filter)
+
+ for exclude in excludes:
+ queryset = queryset.exclude(**exclude)
+
+ connection = connections[queryset.db]
+
+ self.sql, self.params = queryset._as_sql(connection)
+ headers, result, sql = _explain(connection)
+
+ context = {
+ 'select_result': queryset.values_list(),
+ 'explain_result': result,
+ 'sql': sql,
+ 'duration': settings.DEBUG and connection.queries[-1]['time'] or 0.0,
+ 'select_headers': [f.name for f in model._meta.fields], #maybe local_fields is better.
+ 'explain_headers': headers,
+ }
+ return context
+
+
+def dict_models():
+ return dict([(model.__name__, model) for model in django_get_models()])
+
+def list_models():
+ return [["%s|%s" % (model._meta.app_label, model._meta.module_name),
+ "%s - %s" % (model._meta.app_label, model._meta.module_name)]
+ for model in django_get_models() ]
+
+def get_model(model_string):
+ return django_get_model(*model_string.split("|"))
+
+def detail_model(model):
+ managers = [name for id, name, manager in chain(model._meta.concrete_managers,
+ model._meta.abstract_managers)
+ if not name.startswith('_')]
+ fields = [field.name for field in model._meta.local_fields
+ if not isinstance(field, ForeignKey)]
+ # handle fields with relation (fk, m2m)
+ related_fields = [field for field in model._meta.local_fields
+ if isinstance(field, ForeignKey)]
+ related_fields = [(field.name, field.rel.to._meta.app_label, field.rel.to._meta.module_name) \
+ for field in chain(model._meta.many_to_many, related_fields)]
+ return dict(managers=managers, fields=fields, related_fields=related_fields)
+
View
63 query_analyzer/templates/query_analyzer/analyzer.css
@@ -0,0 +1,63 @@
+/* this is pygments.css */
+
+.syntax .hll { background-color: #ffffcc }
+.syntax .c { color: #60a0b0; font-style: italic } /* Comment */
+.syntax .err { border: 1px solid #FF0000 } /* Error */
+.syntax .k { color: #007020; font-weight: bold } /* Keyword */
+.syntax .o { color: #666666 } /* Operator */
+.syntax .cm { color: #60a0b0; font-style: italic } /* Comment.Multiline */
+.syntax .cp { color: #007020 } /* Comment.Preproc */
+.syntax .c1 { color: #60a0b0; font-style: italic } /* Comment.Single */
+.syntax .cs { color: #60a0b0; background-color: #fff0f0 } /* Comment.Special */
+.syntax .gd { color: #A00000 } /* Generic.Deleted */
+.syntax .ge { font-style: italic } /* Generic.Emph */
+.syntax .gr { color: #FF0000 } /* Generic.Error */
+.syntax .gh { color: #000080; font-weight: bold } /* Generic.Heading */
+.syntax .gi { color: #00A000 } /* Generic.Inserted */
+.syntax .go { color: #808080 } /* Generic.Output */
+.syntax .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
+.syntax .gs { font-weight: bold } /* Generic.Strong */
+.syntax .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
+.syntax .gt { color: #0040D0 } /* Generic.Traceback */
+.syntax .kc { color: #007020; font-weight: bold } /* Keyword.Constant */
+.syntax .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
+.syntax .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
+.syntax .kp { color: #007020 } /* Keyword.Pseudo */
+.syntax .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
+.syntax .kt { color: #902000 } /* Keyword.Type */
+.syntax .m { color: #40a070 } /* Literal.Number */
+.syntax .s { color: #4070a0 } /* Literal.String */
+.syntax .na { color: #4070a0 } /* Name.Attribute */
+.syntax .nb { color: #007020 } /* Name.Builtin */
+.syntax .nc { color: #0e84b5; font-weight: bold } /* Name.Class */
+.syntax .no { color: #60add5 } /* Name.Constant */
+.syntax .nd { color: #555555; font-weight: bold } /* Name.Decorator */
+.syntax .ni { color: #d55537; font-weight: bold } /* Name.Entity */
+.syntax .ne { color: #007020 } /* Name.Exception */
+.syntax .nf { color: #06287e } /* Name.Function */
+.syntax .nl { color: #002070; font-weight: bold } /* Name.Label */
+.syntax .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
+.syntax .nt { color: #062873; font-weight: bold } /* Name.Tag */
+.syntax .nv { color: #bb60d5 } /* Name.Variable */
+.syntax .ow { color: #007020; font-weight: bold } /* Operator.Word */
+.syntax .w { color: #bbbbbb } /* Text.Whitespace */
+.syntax .mf { color: #40a070 } /* Literal.Number.Float */
+.syntax .mh { color: #40a070 } /* Literal.Number.Hex */
+.syntax .mi { color: #40a070 } /* Literal.Number.Integer */
+.syntax .mo { color: #40a070 } /* Literal.Number.Oct */
+.syntax .sb { color: #4070a0 } /* Literal.String.Backtick */
+.syntax .sc { color: #4070a0 } /* Literal.String.Char */
+.syntax .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
+.syntax .s2 { color: #4070a0 } /* Literal.String.Double */
+.syntax .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
+.syntax .sh { color: #4070a0 } /* Literal.String.Heredoc */
+.syntax .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
+.syntax .sx { color: #c65d09 } /* Literal.String.Other */
+.syntax .sr { color: #235388 } /* Literal.String.Regex */
+.syntax .s1 { color: #4070a0 } /* Literal.String.Single */
+.syntax .ss { color: #517918 } /* Literal.String.Symbol */
+.syntax .bp { color: #007020 } /* Name.Builtin.Pseudo */
+.syntax .vc { color: #bb60d5 } /* Name.Variable.Class */
+.syntax .vg { color: #bb60d5 } /* Name.Variable.Global */
+.syntax .vi { color: #bb60d5 } /* Name.Variable.Instance */
+.syntax .il { color: #40a070 } /* Literal.Number.Integer.Long */
View
81 query_analyzer/templates/query_analyzer/basic_analyze.html
@@ -0,0 +1,81 @@
+{% extends "admin/base_site.html" %}
+{% load i18n %}
+
+{% block title %}
+ Django | Query Analyzer
+{% endblock %}
+
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="{% url admin:index %}">{% trans 'Home' %}</a> &rsaquo; {{ title }} Query Analyzer</div>{% endblock %}
+
+
+{% block extrahead %}
+<style type="text/css">
+ {% include "query_analyzer/analyzer.css" %}
+</style>
+{% endblock %}
+
+{% block content %}
+ <h1>Select model and enter query</h1>
+ <form method="post">
+ <table>
+ {{ form.as_table }}
+ <tr><td /><td><input type="submit" value="Run" /></td></tr>
+ </table>
+ </form>
+ <br />
+ {% if nice_python %}
+ <h2>Python</h2>
+ <pre class="syntax">{{ nice_python|safe }}</pre>
+ {% endif %}
+
+ {% if sql %}
+ <h2>SQL</h2>
+ <p>The query executed in {{ duration }} milliseconds.</p>
+ <pre class="syntax">{{ nice_sql|safe }}</pre>
+
+
+ <h2>{{ select_result|length }} row{{ select_result|pluralize }} returned</h2>
+ {% if select_result %}
+ <table>
+ <thead>
+ <tr>
+ {% for h in select_headers %}
+ <th>{{ h|upper }}</th>
+ {% endfor %}
+ </tr>
+ </thead>
+ <tbody>
+ {% for row in select_result %}
+ <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %}">
+ {% for column in row %}
+ <td>{{ column|escape }}</td>
+ {% endfor %}
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ {% endif %}
+
+ {% if explain_result %}
+ <h2>Explanation</h2>
+ <table>
+ <thead>
+ <tr>
+ {% for h in explain_headers %}
+ <th>{{ h|upper }}</th>
+ {% endfor %}
+ </tr>
+ </thead>
+ <tbody>
+ {% for row in explain_result %}
+ <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %}">
+ {% for column in row %}
+ <td>{{ column|escape }}</td>
+ {% endfor %}
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ {% endif %}
+ {% endif %}
+{% endblock %}

1 comment on commit 0c5615e

@fitzgen

Very cool, Andy!

I love the direction you have taken this!

Based on the screenshot, it looks like you axed the "pick your manager" functionality; I don't think it would be too hard to only show those controls if there are multiple managers defined. Should be pretty easy to reuse code that Stephan wrote. What do you think?

I am going to have to look through this code some more later, I have a presentation on server side JS and CouchDB to give to my fellow students today which is really stealing all of my focus.

Great Job!

Please sign in to comment.