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.