Index: docs/faq/models.txt
===================================================================
--- docs/faq/models.txt	(revision 15995)
+++ docs/faq/models.txt	(working copy)
@@ -22,9 +22,6 @@
 
 ``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.
 
 .. versionadded:: 1.2
 
Index: django/db/backends/sqlite3/base.py
===================================================================
--- django/db/backends/sqlite3/base.py	(revision 15995)
+++ django/db/backends/sqlite3/base.py	(working copy)
@@ -104,6 +104,18 @@
     def drop_foreignkey_sql(self):
         return ""
 
+    def last_executed_query(self, cursor, sql, params):
+        def quote_params(params):
+            sql = 'SELECT ' + ', '.join(['QUOTE(?)'] * len(params))
+            # use a new cursor instead of the existing cursor wrapper
+            # to avoid recursive logging
+            return cursor.connection.execute(sql, params).fetchone()
+        if isinstance(params, (list, tuple)):
+            params = quote_params(params)
+        else:
+            params = dict(zip(params.keys(), quote_params(params.values())))
+        return super(DatabaseOperations, self).last_executed_query(cursor, sql, params)
+
     def pk_default_value(self):
         return 'NULL'
 
Index: django/db/backends/mysql/base.py
===================================================================
--- django/db/backends/mysql/base.py	(revision 15995)
+++ django/db/backends/mysql/base.py	(working copy)
@@ -189,6 +189,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
Index: django/db/backends/oracle/base.py
===================================================================
--- django/db/backends/oracle/base.py	(revision 15995)
+++ django/db/backends/oracle/base.py	(working copy)
@@ -208,6 +208,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 = get_sequence_name(table_name)
         cursor.execute('SELECT "%s".currval FROM dual' % sq_name)
Index: django/db/backends/postgresql_psycopg2/operations.py
===================================================================
--- django/db/backends/postgresql_psycopg2/operations.py	(revision 15995)
+++ django/db/backends/postgresql_psycopg2/operations.py	(working copy)
@@ -203,9 +203,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):
Index: tests/regressiontests/backends/tests.py
===================================================================
--- tests/regressiontests/backends/tests.py	(revision 15995)
+++ tests/regressiontests/backends/tests.py	(working copy)
@@ -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,33 @@
         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
+
+    @unittest.skipUnless(connection.vendor in ('oracle', 'postgresql', 'sqlite'),
+                         "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)"
