-
Notifications
You must be signed in to change notification settings - Fork 155
Expand file tree
/
Copy pathsql.py
More file actions
163 lines (131 loc) · 5.48 KB
/
sql.py
File metadata and controls
163 lines (131 loc) · 5.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
"""
Based on initial work from django-debug-toolbar
"""
import re
from datetime import datetime
try:
from django.db import connections
except ImportError:
# Django version < 1.2
from django.db import connection
connections = {'default': connection}
try:
from django.db.backends import utils # renamed in django 1.7
except ImportError:
from django.db.backends import util as utils # removed in django 1.9
from django.conf import settings as django_settings
#from django.template import Node
from devserver.modules import DevServerModule
#from devserver.utils.stack import tidy_stacktrace, get_template_info
from devserver.utils.time import ms_from_timedelta
from devserver import settings
try:
import sqlparse
except ImportError:
class sqlparse:
@staticmethod
def format(text, *args, **kwargs):
return text
_sql_fields_re = re.compile(r'SELECT .*? FROM')
_sql_aggregates_re = re.compile(r'SELECT .*?(COUNT|SUM|AVERAGE|MIN|MAX).*? FROM')
def truncate_sql(sql, aggregates=True):
if not aggregates and _sql_aggregates_re.match(sql):
return sql
return _sql_fields_re.sub('SELECT ... FROM', sql)
# # TODO:This should be set in the toolbar loader as a default and panels should
# # get a copy of the toolbar object with access to its config dictionary
# SQL_WARNING_THRESHOLD = getattr(settings, 'DEVSERVER_CONFIG', {}) \
# .get('SQL_WARNING_THRESHOLD', 500)
try:
from debug_toolbar.panels.sql import DatabaseStatTracker
debug_toolbar = True
except ImportError:
debug_toolbar = False
import django
#version = float('.'.join([str(x) for x in django.VERSION[:2]]))
#if version >= 1.6:
# Version comparison fix required after Django 1.10
version = django.VERSION[0] * 100 + django.VERSION[1]
if version >= 106:
DatabaseStatTracker = utils.CursorWrapper
else:
DatabaseStatTracker = utils.CursorDebugWrapper
class DatabaseStatTracker(DatabaseStatTracker):
"""
Replacement for CursorDebugWrapper which outputs information as it happens.
"""
logger = None
def execute(self, sql, params=()):
formatted_sql = sql % (params if isinstance(params, dict) else tuple(params))
if self.logger:
message = formatted_sql
if settings.DEVSERVER_FILTER_SQL:
if any(filter_.search(message) for filter_ in settings.DEVSERVER_FILTER_SQL):
message = None
if message is not None:
if settings.DEVSERVER_TRUNCATE_SQL:
message = truncate_sql(message, aggregates=settings.DEVSERVER_TRUNCATE_AGGREGATES)
message = sqlparse.format(message, reindent=True, keyword_case='upper')
self.logger.debug(message)
start = datetime.now()
try:
return super(DatabaseStatTracker, self).execute(sql, params)
finally:
stop = datetime.now()
duration = ms_from_timedelta(stop - start)
if self.logger and (not settings.DEVSERVER_SQL_MIN_DURATION
or duration > settings.DEVSERVER_SQL_MIN_DURATION):
if self.cursor.rowcount >= 0 and message is not None:
self.logger.debug('Found %s matching rows', self.cursor.rowcount, duration=duration)
if not (debug_toolbar or django_settings.DEBUG):
self.db.queries.append({
'sql': formatted_sql,
'time': duration,
})
def executemany(self, sql, param_list):
start = datetime.now()
try:
return super(DatabaseStatTracker, self).executemany(sql, param_list)
finally:
stop = datetime.now()
duration = ms_from_timedelta(stop - start)
if self.logger:
message = sqlparse.format(sql, reindent=True, keyword_case='upper')
message = 'Executed %s times\n' % message
self.logger.debug(message, duration=duration)
self.logger.debug('Found %s matching rows', self.cursor.rowcount, duration=duration, id='query')
if not (debug_toolbar or settings.DEBUG):
self.db.queries.append({
'sql': '%s times: %s' % (len(param_list), sql),
'time': duration,
})
class SQLRealTimeModule(DevServerModule):
"""
Outputs SQL queries as they happen.
"""
logger_name = 'sql'
def process_init(self, request):
if not issubclass(utils.CursorDebugWrapper, DatabaseStatTracker):
self.old_cursor = utils.CursorDebugWrapper
utils.CursorDebugWrapper = DatabaseStatTracker
DatabaseStatTracker.logger = self.logger
def process_complete(self, request):
if issubclass(utils.CursorDebugWrapper, DatabaseStatTracker):
utils.CursorDebugWrapper = self.old_cursor
class SQLSummaryModule(DevServerModule):
"""
Outputs a summary SQL queries.
"""
logger_name = 'sql'
def process_complete(self, request):
queries = [
q for alias in connections
for q in connections[alias].queries
]
num_queries = len(queries)
if num_queries:
unique = set([s['sql'] for s in queries])
self.logger.info('%(calls)s queries with %(dupes)s duplicates' % dict(
calls=num_queries,
dupes=num_queries - len(unique),
), duration=sum(float(c.get('time', 0)) for c in queries) * 1000)