Russell Keith-Magee wrote:
> I would suggest approaching this problem at lower than that -
> installing a filter at the level of the database cursor that diverts
> queries away from the actual database, and into a store. That way, if
> you run the code 'Author.objects.all()' , the backend will get the
> request to invoke 'SELECT * FROM Author', but this will get recorded
> rather than sent to the database.
> You then add a --sql flag to ./manage.py that sets up the recording
> mode on the database backend, and outputs the command buffer at the
> end of execution. If you make this interface generic, anyone could
> invoke SQL recording whenever they want.
> Part of this infrastructure is already in place for logging purposes.
> Improvements to the logging capability that allow for recording and
> playback would be most welcome.
How about the patch below? When you create the cursor, if you want
access to "don't run this SQL, just have playback available", just use
connection.cursor(playback_only=True), and if you want to roll the
playback, you can either use self.db.queries directly, or use the
util.CursorDebugWrapper.playback method. Totally backwards-compatible
and shouldn't step on any toes (uses existing logging, like you
mentioned above). You can easily add this into
django/core/management.py. If this is okay, I'll do a patch for all the
existing backends, it's a simple change now that I've looked through
everything, assuming there's no changes related to the next paragraph.
Something that I found a little irksome while working on this is that
the DatabaseWrapper class for each backend doesn't inherit from some
logical parent. I know that all the db-specific functionality is
wrapped by these classes, but there are things that are in each class
that are most definitely shared functionality, like the mechanism by
which util.CursorDebugWrapper is instantiated. We could move that off
to a method in a base class very nicely, and then the playback_only
addition wouldn't have to be added to every backend, or the change would
be more minimal. Also, I think it might be appropriate to have
"playback()" be a method of the DatabaseWrapper (connection) and not the
CursorDebugWrapper, since if there's any DB-specific separators we need
to use, there's no way to deal with that from the cursor. But, if we
don't make a base class, then we're going to have to duplicate
playback's code in all the backend/(db)/base.py files. Not very DRY.
Thanks,
George
--- django_orig/django/db/backends/mysql/base.py 2007-08-02
20:59:29.000000000 -0400
+++ django_live/django/db/backends/mysql/base.py 2007-08-12
09:08:00.000000000 -0400
@@ -77,7 +77,7 @@ class DatabaseWrapper(local):
self.connection = None
return False
- def cursor(self):
+ def cursor(self, playback_only=False):
from django.conf import settings
from warnings import filterwarnings
if not self._valid_connection():
@@ -103,9 +103,9 @@ class DatabaseWrapper(local):
cursor = self.connection.cursor()
else:
cursor = self.connection.cursor()
- if settings.DEBUG:
+ if settings.DEBUG or playback_only:
filterwarnings("error", category=Database.Warning)
- return util.CursorDebugWrapper(cursor, self)
+ return util.CursorDebugWrapper(cursor, self,
playback_only=playback_only)
return cursor
def _commit(self):
--- django_orig/django/db/backends/util.py 2007-08-02
20:59:29.000000000 -0400
+++ django_live/django/db/backends/util.py 2007-08-12
09:01:02.000000000 -0400
@@ -9,14 +9,16 @@ except ImportError:
from django.utils import _decimal as decimal # for Python 2.3
class CursorDebugWrapper(object):
- def __init__(self, cursor, db):
+ def __init__(self, cursor, db, playback_only=False):
self.cursor = cursor
self.db = db
+ self.allow_execute = not playback_only
def execute(self, sql, params=()):
start = time()
try:
- return self.cursor.execute(sql, params)
+ if self.allow_execute:
+ return self.cursor.execute(sql, params)
finally:
stop = time()
self.db.queries.append({
@@ -27,7 +29,8 @@ class CursorDebugWrapper(object):
def executemany(self, sql, param_list):
start = time()
try:
- return self.cursor.executemany(sql, param_list)
+ if self.allow_execute:
+ return self.cursor.executemany(sql, param_list)
finally:
stop = time()
self.db.queries.append({
@@ -40,6 +43,10 @@ class CursorDebugWrapper(object):
return self.__dict__[attr]
else:
return getattr(self.cursor, attr)
+
+ def playback(self):
+ return ';'.join([query['sql'] for query in self.db.queries])
+
def convert_args(args):
"""