Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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

…eries.

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
  • Loading branch information...
commit 5b0e4e49d4ab5e976fbfdde70c525a13220f3259 1 parent 598032b
Jacob Kaplan-Moss authored April 22, 2011
1  AUTHORS
@@ -60,6 +60,7 @@ answer newbie questions, and generally made Django that much better:
60 60
     atlithorn <atlithorn@gmail.com>
61 61
     Jökull Sólberg Auðunsson <jokullsolberg@gmail.com>
62 62
     Arthur <avandorp@gmail.com>
  63
+    Aymeric Augustin <aymeric.augustin@m4x.org>
63 64
     av0000@mail.ru
64 65
     David Avsajanishvili <avsd05@gmail.com>
65 66
     Mike Axiak <axiak@mit.edu>
6  django/db/backends/mysql/base.py
@@ -191,6 +191,12 @@ def force_no_ordering(self):
191 191
     def fulltext_search_sql(self, field_name):
192 192
         return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name
193 193
 
  194
+    def last_executed_query(self, cursor, sql, params):
  195
+        # With MySQLdb, cursor objects have an (undocumented) "_last_executed"
  196
+        # attribute where the exact query sent to the database is saved.
  197
+        # See MySQLdb/cursors.py in the source distribution.
  198
+        return cursor._last_executed
  199
+
194 200
     def no_limit_value(self):
195 201
         # 2**64 - 1, as recommended by the MySQL documentation
196 202
         return 18446744073709551615L
5  django/db/backends/oracle/base.py
@@ -210,6 +210,11 @@ def field_cast_sql(self, db_type):
210 210
         else:
211 211
             return "%s"
212 212
 
  213
+    def last_executed_query(self, cursor, sql, params):
  214
+        # http://cx-oracle.sourceforge.net/html/cursor.html#Cursor.statement
  215
+        # The DB API definition does not define this attribute.
  216
+        return cursor.statement
  217
+
213 218
     def last_insert_id(self, cursor, table_name, pk_name):
214 219
         sq_name = self._get_sequence_name(table_name)
215 220
         cursor.execute('SELECT "%s".currval FROM dual' % sq_name)
5  django/db/backends/postgresql_psycopg2/operations.py
@@ -202,9 +202,8 @@ def max_name_length(self):
202 202
         return 63
203 203
 
204 204
     def last_executed_query(self, cursor, sql, params):
205  
-        # With psycopg2, cursor objects have a "query" attribute that is the
206  
-        # exact query sent to the database. See docs here:
207  
-        # http://www.initd.org/tracker/psycopg/wiki/psycopg2_documentation#postgresql-status-message-and-executed-query
  205
+        # http://initd.org/psycopg/docs/cursor.html#cursor.query
  206
+        # The query attribute is a Psycopg extension to the DB API 2.0.
208 207
         return cursor.query
209 208
 
210 209
     def return_insert_id(self):
5  docs/faq/models.txt
@@ -22,9 +22,8 @@ of dictionaries in order of query execution. Each dictionary has the following::
22 22
 
23 23
 ``connection.queries`` includes all SQL statements -- INSERTs, UPDATES,
24 24
 SELECTs, etc. Each time your app hits the database, the query will be recorded.
25  
-Note that the raw SQL logged in ``connection.queries`` may not include
26  
-parameter quoting.  Parameter quoting is performed by the database-specific
27  
-backend, and not all backends provide a way to retrieve the SQL after quoting.
  25
+Note that the SQL recorded here may be :ref:`incorrectly quoted under SQLite
  26
+<sqlite-connection-queries>`.
28 27
 
29 28
 .. versionadded:: 1.2
30 29
 
12  docs/ref/databases.txt
@@ -465,7 +465,7 @@ itself to versions newer than the ones included with your particular Python
465 465
 binary distribution, if needed.
466 466
 
467 467
 "Database is locked" errors
468  
------------------------------------------------
  468
+---------------------------
469 469
 
470 470
 SQLite is meant to be a lightweight database, and thus can't support a high
471 471
 level of concurrency. ``OperationalError: database is locked`` errors indicate
@@ -506,6 +506,16 @@ If you're getting this error, you can solve it by:
506 506
 SQLite does not support the ``SELECT ... FOR UPDATE`` syntax. Calling it will
507 507
 have no effect.
508 508
 
  509
+.. _sqlite-connection-queries:
  510
+
  511
+Parameters not quoted in ``connection.queries``
  512
+-----------------------------------------------
  513
+
  514
+``sqlite3`` does not provide a way to retrieve the SQL after quoting and
  515
+substituting the parameters. Instead, the SQL in ``connection.queries`` is
  516
+rebuilt with a simple string interpolation. It may be incorrect. Make sure
  517
+you add quotes where necessary before copying a query into a SQLite shell.
  518
+
509 519
 .. _oracle-notes:
510 520
 
511 521
 Oracle notes
30  tests/regressiontests/backends/tests.py
@@ -2,6 +2,7 @@
2 2
 # Unit and doctests for specific database backends.
3 3
 import datetime
4 4
 
  5
+from django.conf import settings
5 6
 from django.core.management.color import no_style
6 7
 from django.db import backend, connection, connections, DEFAULT_DB_ALIAS, IntegrityError
7 8
 from django.db.backends.signals import connection_created
@@ -85,6 +86,35 @@ def test_django_extract(self):
85 86
         classes = models.SchoolClass.objects.filter(last_updated__day=20)
86 87
         self.assertEqual(len(classes), 1)
87 88
 
  89
+class LastExecutedQueryTest(TestCase):
  90
+
  91
+    def setUp(self):
  92
+        # connection.queries will not be filled in without this
  93
+        settings.DEBUG = True
  94
+
  95
+    def tearDown(self):
  96
+        settings.DEBUG = False
  97
+
  98
+    # There are no tests for the sqlite backend because it does not
  99
+    # implement paramater escaping. See #14091.
  100
+
  101
+    @unittest.skipUnless(connection.vendor in ('oracle', 'postgresql'),
  102
+                         "These backends use the standard parameter escaping rules")
  103
+    def test_parameter_escaping(self):
  104
+        # check that both numbers and string are properly quoted
  105
+        list(models.Tag.objects.filter(name="special:\\\"':", object_id=12))
  106
+        sql = connection.queries[-1]['sql']
  107
+        self.assertTrue("= 'special:\\\"'':' " in sql)
  108
+        self.assertTrue("= 12 " in sql)
  109
+
  110
+    @unittest.skipUnless(connection.vendor == 'mysql',
  111
+                         "MySQL uses backslashes to escape parameters.")
  112
+    def test_parameter_escaping(self):
  113
+        list(models.Tag.objects.filter(name="special:\\\"':", object_id=12))
  114
+        sql = connection.queries[-1]['sql']
  115
+        # only this line is different from the test above
  116
+        self.assertTrue("= 'special:\\\\\\\"\\':' " in sql)
  117
+        self.assertTrue("= 12 " in sql)
88 118
 
89 119
 class ParameterHandlingTest(TestCase):
90 120
     def test_bad_parameter_count(self):

0 notes on commit 5b0e4e4

Please sign in to comment.
Something went wrong with that request. Please try again.