2016-09-18 02:29:00 +03:00
|
|
|
# Code obtained from django-debug-toolbar sql panel tracking
|
|
|
|
from __future__ import absolute_import, unicode_literals
|
|
|
|
|
|
|
|
import json
|
|
|
|
from threading import local
|
|
|
|
from time import time
|
|
|
|
|
2020-01-21 00:05:20 +03:00
|
|
|
from django.utils.encoding import force_str
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
from .types import DjangoDebugSQL
|
|
|
|
|
|
|
|
|
|
|
|
class SQLQueryTriggered(Exception):
|
|
|
|
"""Thrown when template panel triggers a query"""
|
|
|
|
|
|
|
|
|
|
|
|
class ThreadLocalState(local):
|
|
|
|
def __init__(self):
|
|
|
|
self.enabled = True
|
|
|
|
|
|
|
|
@property
|
|
|
|
def Wrapper(self):
|
|
|
|
if self.enabled:
|
|
|
|
return NormalCursorWrapper
|
|
|
|
return ExceptionCursorWrapper
|
|
|
|
|
|
|
|
def recording(self, v):
|
|
|
|
self.enabled = v
|
|
|
|
|
|
|
|
|
|
|
|
state = ThreadLocalState()
|
|
|
|
recording = state.recording # export function
|
|
|
|
|
|
|
|
|
|
|
|
def wrap_cursor(connection, panel):
|
2018-07-20 02:51:33 +03:00
|
|
|
if not hasattr(connection, "_graphene_cursor"):
|
2016-09-18 02:29:00 +03:00
|
|
|
connection._graphene_cursor = connection.cursor
|
|
|
|
|
|
|
|
def cursor():
|
|
|
|
return state.Wrapper(connection._graphene_cursor(), connection, panel)
|
|
|
|
|
|
|
|
connection.cursor = cursor
|
|
|
|
return cursor
|
|
|
|
|
|
|
|
|
|
|
|
def unwrap_cursor(connection):
|
2018-07-20 02:51:33 +03:00
|
|
|
if hasattr(connection, "_graphene_cursor"):
|
2016-09-18 02:29:00 +03:00
|
|
|
previous_cursor = connection._graphene_cursor
|
|
|
|
connection.cursor = previous_cursor
|
|
|
|
del connection._graphene_cursor
|
|
|
|
|
|
|
|
|
|
|
|
class ExceptionCursorWrapper(object):
|
|
|
|
"""
|
|
|
|
Wraps a cursor and raises an exception on any operation.
|
|
|
|
Used in Templates panel.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, cursor, db, logger):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def __getattr__(self, attr):
|
|
|
|
raise SQLQueryTriggered()
|
|
|
|
|
|
|
|
|
|
|
|
class NormalCursorWrapper(object):
|
|
|
|
"""
|
|
|
|
Wraps a cursor and logs queries.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, cursor, db, logger):
|
|
|
|
self.cursor = cursor
|
|
|
|
# Instance of a BaseDatabaseWrapper subclass
|
|
|
|
self.db = db
|
|
|
|
# logger must implement a ``record`` method
|
|
|
|
self.logger = logger
|
|
|
|
|
|
|
|
def _quote_expr(self, element):
|
2020-04-06 15:21:07 +03:00
|
|
|
if isinstance(element, str):
|
2020-01-21 00:05:20 +03:00
|
|
|
return "'%s'" % force_str(element).replace("'", "''")
|
2016-09-18 02:29:00 +03:00
|
|
|
else:
|
|
|
|
return repr(element)
|
|
|
|
|
|
|
|
def _quote_params(self, params):
|
|
|
|
if not params:
|
|
|
|
return params
|
|
|
|
if isinstance(params, dict):
|
2018-07-20 02:51:33 +03:00
|
|
|
return dict((key, self._quote_expr(value)) for key, value in params.items())
|
2016-09-18 02:29:00 +03:00
|
|
|
return list(map(self._quote_expr, params))
|
|
|
|
|
|
|
|
def _decode(self, param):
|
|
|
|
try:
|
2020-01-21 00:05:20 +03:00
|
|
|
return force_str(param, strings_only=True)
|
2016-09-18 02:29:00 +03:00
|
|
|
except UnicodeDecodeError:
|
2018-07-20 02:51:33 +03:00
|
|
|
return "(encoded string)"
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
def _record(self, method, sql, params):
|
|
|
|
start_time = time()
|
|
|
|
try:
|
|
|
|
return method(sql, params)
|
|
|
|
finally:
|
|
|
|
stop_time = time()
|
2018-07-20 02:51:33 +03:00
|
|
|
duration = stop_time - start_time
|
|
|
|
_params = ""
|
2016-09-18 02:29:00 +03:00
|
|
|
try:
|
|
|
|
_params = json.dumps(list(map(self._decode, params)))
|
|
|
|
except Exception:
|
|
|
|
pass # object not JSON serializable
|
|
|
|
|
2018-07-20 02:51:33 +03:00
|
|
|
alias = getattr(self.db, "alias", "default")
|
2016-09-18 02:29:00 +03:00
|
|
|
conn = self.db.connection
|
2018-07-20 02:51:33 +03:00
|
|
|
vendor = getattr(conn, "vendor", "unknown")
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
params = {
|
2018-07-20 02:51:33 +03:00
|
|
|
"vendor": vendor,
|
|
|
|
"alias": alias,
|
|
|
|
"sql": self.db.ops.last_executed_query(
|
|
|
|
self.cursor, sql, self._quote_params(params)
|
|
|
|
),
|
|
|
|
"duration": duration,
|
|
|
|
"raw_sql": sql,
|
|
|
|
"params": _params,
|
|
|
|
"start_time": start_time,
|
|
|
|
"stop_time": stop_time,
|
|
|
|
"is_slow": duration > 10,
|
|
|
|
"is_select": sql.lower().strip().startswith("select"),
|
2016-09-18 02:29:00 +03:00
|
|
|
}
|
|
|
|
|
2018-07-20 02:51:33 +03:00
|
|
|
if vendor == "postgresql":
|
2016-09-18 02:29:00 +03:00
|
|
|
# If an erroneous query was ran on the connection, it might
|
|
|
|
# be in a state where checking isolation_level raises an
|
|
|
|
# exception.
|
|
|
|
try:
|
|
|
|
iso_level = conn.isolation_level
|
|
|
|
except conn.InternalError:
|
2018-07-20 02:51:33 +03:00
|
|
|
iso_level = "unknown"
|
|
|
|
params.update(
|
|
|
|
{
|
|
|
|
"trans_id": self.logger.get_transaction_id(alias),
|
|
|
|
"trans_status": conn.get_transaction_status(),
|
|
|
|
"iso_level": iso_level,
|
|
|
|
"encoding": conn.encoding,
|
|
|
|
}
|
|
|
|
)
|
2016-09-18 02:29:00 +03:00
|
|
|
|
|
|
|
_sql = DjangoDebugSQL(**params)
|
|
|
|
# We keep `sql` to maintain backwards compatibility
|
|
|
|
self.logger.object.sql.append(_sql)
|
|
|
|
|
2020-05-21 18:16:14 +03:00
|
|
|
def callproc(self, procname, params=None):
|
2016-09-18 02:29:00 +03:00
|
|
|
return self._record(self.cursor.callproc, procname, params)
|
|
|
|
|
2020-05-21 18:16:14 +03:00
|
|
|
def execute(self, sql, params=None):
|
2016-09-18 02:29:00 +03:00
|
|
|
return self._record(self.cursor.execute, sql, params)
|
|
|
|
|
|
|
|
def executemany(self, sql, param_list):
|
|
|
|
return self._record(self.cursor.executemany, sql, param_list)
|
|
|
|
|
|
|
|
def __getattr__(self, attr):
|
|
|
|
return getattr(self.cursor, attr)
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
return iter(self.cursor)
|
|
|
|
|
|
|
|
def __enter__(self):
|
|
|
|
return self
|
|
|
|
|
|
|
|
def __exit__(self, type, value, traceback):
|
|
|
|
self.close()
|