Author: jacob
Date: 2011-04-22 05:14:54 -0700 (Fri, 22 Apr 2011)
New Revision: 16081

Modified:
   django/trunk/AUTHORS
   django/trunk/django/db/backends/mysql/base.py
   django/trunk/django/db/backends/oracle/base.py
   django/trunk/django/db/backends/postgresql_psycopg2/operations.py
   django/trunk/docs/faq/models.txt
   django/trunk/docs/ref/databases.txt
   django/trunk/tests/regressiontests/backends/tests.py
Log:
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.

Modified: django/trunk/AUTHORS
===================================================================
--- django/trunk/AUTHORS        2011-04-22 12:06:11 UTC (rev 16080)
+++ django/trunk/AUTHORS        2011-04-22 12:14:54 UTC (rev 16081)
@@ -60,6 +60,7 @@
     atlithorn <atlith...@gmail.com>
     Jökull Sólberg Auðunsson <jokullsolb...@gmail.com>
     Arthur <avand...@gmail.com>
+    Aymeric Augustin <aymeric.augus...@m4x.org>
     av0...@mail.ru
     David Avsajanishvili <avs...@gmail.com>
     Mike Axiak <ax...@mit.edu>

Modified: django/trunk/django/db/backends/mysql/base.py
===================================================================
--- django/trunk/django/db/backends/mysql/base.py       2011-04-22 12:06:11 UTC 
(rev 16080)
+++ django/trunk/django/db/backends/mysql/base.py       2011-04-22 12:14:54 UTC 
(rev 16081)
@@ -191,6 +191,12 @@
     def fulltext_search_sql(self, 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):
         # 2**64 - 1, as recommended by the MySQL documentation
         return 18446744073709551615L

Modified: django/trunk/django/db/backends/oracle/base.py
===================================================================
--- django/trunk/django/db/backends/oracle/base.py      2011-04-22 12:06:11 UTC 
(rev 16080)
+++ django/trunk/django/db/backends/oracle/base.py      2011-04-22 12:14:54 UTC 
(rev 16081)
@@ -210,6 +210,11 @@
         else:
             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):
         sq_name = self._get_sequence_name(table_name)
         cursor.execute('SELECT "%s".currval FROM dual' % sq_name)

Modified: django/trunk/django/db/backends/postgresql_psycopg2/operations.py
===================================================================
--- django/trunk/django/db/backends/postgresql_psycopg2/operations.py   
2011-04-22 12:06:11 UTC (rev 16080)
+++ django/trunk/django/db/backends/postgresql_psycopg2/operations.py   
2011-04-22 12:14:54 UTC (rev 16081)
@@ -202,9 +202,8 @@
         return 63
 
     def last_executed_query(self, cursor, sql, params):
-        # With psycopg2, cursor objects have a "query" attribute that is the
-        # exact query sent to the database. See docs here:
-        # 
http://www.initd.org/tracker/psycopg/wiki/psycopg2_documentation#postgresql-status-message-and-executed-query
+        # http://initd.org/psycopg/docs/cursor.html#cursor.query
+        # The query attribute is a Psycopg extension to the DB API 2.0.
         return cursor.query
 
     def return_insert_id(self):

Modified: django/trunk/docs/faq/models.txt
===================================================================
--- django/trunk/docs/faq/models.txt    2011-04-22 12:06:11 UTC (rev 16080)
+++ django/trunk/docs/faq/models.txt    2011-04-22 12:14:54 UTC (rev 16081)
@@ -22,9 +22,8 @@
 
 ``connection.queries`` includes all SQL statements -- INSERTs, UPDATES,
 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
-parameter quoting.  Parameter quoting is performed by the database-specific
-backend, and not all backends provide a way to retrieve the SQL after quoting.
+Note that the SQL recorded here may be :ref:`incorrectly quoted under SQLite
+<sqlite-connection-queries>`.
 
 .. versionadded:: 1.2
 

Modified: django/trunk/docs/ref/databases.txt
===================================================================
--- django/trunk/docs/ref/databases.txt 2011-04-22 12:06:11 UTC (rev 16080)
+++ django/trunk/docs/ref/databases.txt 2011-04-22 12:14:54 UTC (rev 16081)
@@ -465,7 +465,7 @@
 binary distribution, if needed.
 
 "Database is locked" errors
------------------------------------------------
+---------------------------
 
 SQLite is meant to be a lightweight database, and thus can't support a high
 level of concurrency. ``OperationalError: database is locked`` errors indicate
@@ -506,6 +506,16 @@
 SQLite does not support the ``SELECT ... FOR UPDATE`` syntax. Calling it will
 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

Modified: django/trunk/tests/regressiontests/backends/tests.py
===================================================================
--- django/trunk/tests/regressiontests/backends/tests.py        2011-04-22 
12:06:11 UTC (rev 16080)
+++ django/trunk/tests/regressiontests/backends/tests.py        2011-04-22 
12:14:54 UTC (rev 16081)
@@ -2,6 +2,7 @@
 # Unit and doctests for specific database backends.
 import datetime
 
+from django.conf import settings
 from django.core.management.color import no_style
 from django.db import backend, connection, connections, DEFAULT_DB_ALIAS, 
IntegrityError
 from django.db.backends.signals import connection_created
@@ -85,7 +86,36 @@
         classes = models.SchoolClass.objects.filter(last_updated__day=20)
         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):
     def test_bad_parameter_count(self):
         "An executemany call with too many/not enough parameters will raise an 
exception (Refs #12612)"

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to django-updates@googlegroups.com.
To unsubscribe from this group, send email to 
django-updates+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to