Fixed #14091 - be more correct about logging queries in connection.queries.

Thanks to Aymeric Augustin for figuring out how to make this work across
multiple databases.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16081 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jacob Kaplan-Moss 2011-04-22 12:14:54 +00:00
parent 598032b8c4
commit 5b0e4e49d4
7 changed files with 57 additions and 7 deletions

View File

@ -60,6 +60,7 @@ answer newbie questions, and generally made Django that much better:
atlithorn <atlithorn@gmail.com> atlithorn <atlithorn@gmail.com>
Jökull Sólberg Auðunsson <jokullsolberg@gmail.com> Jökull Sólberg Auðunsson <jokullsolberg@gmail.com>
Arthur <avandorp@gmail.com> Arthur <avandorp@gmail.com>
Aymeric Augustin <aymeric.augustin@m4x.org>
av0000@mail.ru av0000@mail.ru
David Avsajanishvili <avsd05@gmail.com> David Avsajanishvili <avsd05@gmail.com>
Mike Axiak <axiak@mit.edu> Mike Axiak <axiak@mit.edu>

View File

@ -191,6 +191,12 @@ class DatabaseOperations(BaseDatabaseOperations):
def fulltext_search_sql(self, field_name): def fulltext_search_sql(self, field_name):
return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name
def last_executed_query(self, cursor, sql, params):
# With MySQLdb, cursor objects have an (undocumented) "_last_executed"
# attribute where the exact query sent to the database is saved.
# See MySQLdb/cursors.py in the source distribution.
return cursor._last_executed
def no_limit_value(self): def no_limit_value(self):
# 2**64 - 1, as recommended by the MySQL documentation # 2**64 - 1, as recommended by the MySQL documentation
return 18446744073709551615L return 18446744073709551615L

View File

@ -210,6 +210,11 @@ WHEN (new.%(col_name)s IS NULL)
else: else:
return "%s" return "%s"
def last_executed_query(self, cursor, sql, params):
# http://cx-oracle.sourceforge.net/html/cursor.html#Cursor.statement
# The DB API definition does not define this attribute.
return cursor.statement
def last_insert_id(self, cursor, table_name, pk_name): def last_insert_id(self, cursor, table_name, pk_name):
sq_name = self._get_sequence_name(table_name) sq_name = self._get_sequence_name(table_name)
cursor.execute('SELECT "%s".currval FROM dual' % sq_name) cursor.execute('SELECT "%s".currval FROM dual' % sq_name)

View File

@ -202,9 +202,8 @@ class DatabaseOperations(BaseDatabaseOperations):
return 63 return 63
def last_executed_query(self, cursor, sql, params): def last_executed_query(self, cursor, sql, params):
# With psycopg2, cursor objects have a "query" attribute that is the # http://initd.org/psycopg/docs/cursor.html#cursor.query
# exact query sent to the database. See docs here: # The query attribute is a Psycopg extension to the DB API 2.0.
# http://www.initd.org/tracker/psycopg/wiki/psycopg2_documentation#postgresql-status-message-and-executed-query
return cursor.query return cursor.query
def return_insert_id(self): def return_insert_id(self):

View File

@ -22,9 +22,8 @@ of dictionaries in order of query execution. Each dictionary has the following::
``connection.queries`` includes all SQL statements -- INSERTs, UPDATES, ``connection.queries`` includes all SQL statements -- INSERTs, UPDATES,
SELECTs, etc. Each time your app hits the database, the query will be recorded. SELECTs, etc. Each time your app hits the database, the query will be recorded.
Note that the raw SQL logged in ``connection.queries`` may not include Note that the SQL recorded here may be :ref:`incorrectly quoted under SQLite
parameter quoting. Parameter quoting is performed by the database-specific <sqlite-connection-queries>`.
backend, and not all backends provide a way to retrieve the SQL after quoting.
.. versionadded:: 1.2 .. versionadded:: 1.2

View File

@ -465,7 +465,7 @@ itself to versions newer than the ones included with your particular Python
binary distribution, if needed. binary distribution, if needed.
"Database is locked" errors "Database is locked" errors
----------------------------------------------- ---------------------------
SQLite is meant to be a lightweight database, and thus can't support a high SQLite is meant to be a lightweight database, and thus can't support a high
level of concurrency. ``OperationalError: database is locked`` errors indicate level of concurrency. ``OperationalError: database is locked`` errors indicate
@ -506,6 +506,16 @@ If you're getting this error, you can solve it by:
SQLite does not support the ``SELECT ... FOR UPDATE`` syntax. Calling it will SQLite does not support the ``SELECT ... FOR UPDATE`` syntax. Calling it will
have no effect. have no effect.
.. _sqlite-connection-queries:
Parameters not quoted in ``connection.queries``
-----------------------------------------------
``sqlite3`` does not provide a way to retrieve the SQL after quoting and
substituting the parameters. Instead, the SQL in ``connection.queries`` is
rebuilt with a simple string interpolation. It may be incorrect. Make sure
you add quotes where necessary before copying a query into a SQLite shell.
.. _oracle-notes: .. _oracle-notes:
Oracle notes Oracle notes

View File

@ -2,6 +2,7 @@
# Unit and doctests for specific database backends. # Unit and doctests for specific database backends.
import datetime import datetime
from django.conf import settings
from django.core.management.color import no_style from django.core.management.color import no_style
from django.db import backend, connection, connections, DEFAULT_DB_ALIAS, IntegrityError from django.db import backend, connection, connections, DEFAULT_DB_ALIAS, IntegrityError
from django.db.backends.signals import connection_created from django.db.backends.signals import connection_created
@ -85,6 +86,35 @@ class DateQuotingTest(TestCase):
classes = models.SchoolClass.objects.filter(last_updated__day=20) classes = models.SchoolClass.objects.filter(last_updated__day=20)
self.assertEqual(len(classes), 1) self.assertEqual(len(classes), 1)
class LastExecutedQueryTest(TestCase):
def setUp(self):
# connection.queries will not be filled in without this
settings.DEBUG = True
def tearDown(self):
settings.DEBUG = False
# There are no tests for the sqlite backend because it does not
# implement paramater escaping. See #14091.
@unittest.skipUnless(connection.vendor in ('oracle', 'postgresql'),
"These backends use the standard parameter escaping rules")
def test_parameter_escaping(self):
# check that both numbers and string are properly quoted
list(models.Tag.objects.filter(name="special:\\\"':", object_id=12))
sql = connection.queries[-1]['sql']
self.assertTrue("= 'special:\\\"'':' " in sql)
self.assertTrue("= 12 " in sql)
@unittest.skipUnless(connection.vendor == 'mysql',
"MySQL uses backslashes to escape parameters.")
def test_parameter_escaping(self):
list(models.Tag.objects.filter(name="special:\\\"':", object_id=12))
sql = connection.queries[-1]['sql']
# only this line is different from the test above
self.assertTrue("= 'special:\\\\\\\"\\':' " in sql)
self.assertTrue("= 12 " in sql)
class ParameterHandlingTest(TestCase): class ParameterHandlingTest(TestCase):
def test_bad_parameter_count(self): def test_bad_parameter_count(self):