Commit 4f6a7663 authored by Aymeric Augustin's avatar Aymeric Augustin
Browse files

Refs #14091 -- Fixed connection.queries on SQLite.

parent fc8a6a9b
Loading
Loading
Loading
Loading
+33 −0
Original line number Diff line number Diff line
@@ -103,6 +103,39 @@ class DatabaseOperations(BaseDatabaseOperations):
    def pk_default_value(self):
        return "NULL"

    def _quote_params_for_last_executed_query(self, params):
        """
        Only for last_executed_query! Don't use this to execute SQL queries!
        """
        sql = 'SELECT ' + ', '.join(['QUOTE(?)'] * len(params))
        # Bypass Django's wrappers and use the underlying sqlite3 connection
        # to avoid logging this query - it would trigger infinite recursion.
        cursor = self.connection.connection.cursor()
        # Native sqlite3 cursors cannot be used as context managers.
        try:
            return cursor.execute(sql, params).fetchone()
        finally:
            cursor.close()

    def last_executed_query(self, cursor, sql, params):
        # Python substitutes parameters in Modules/_sqlite/cursor.c with:
        # pysqlite_statement_bind_parameters(self->statement, parameters, allow_8bit_chars);
        # Unfortunately there is no way to reach self->statement from Python,
        # so we quote and substitute parameters manually.
        if params:
            if isinstance(params, (list, tuple)):
                params = self._quote_params_for_last_executed_query(params)
            else:
                keys = params.keys()
                values = tuple(params.values())
                values = self._quote_params_for_last_executed_query(values)
                params = dict(zip(keys, values))
            return sql % params
        # For consistency with SQLiteCursorWrapper.execute(), just return sql
        # when there are no parameters. See #13648 and #17158.
        else:
            return sql

    def quote_name(self, name):
        if name.startswith('"') and name.endswith('"'):
            return name  # Quoting once is enough.
+0 −2
Original line number Diff line number Diff line
@@ -23,8 +23,6 @@ the following::

``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 SQL recorded here may be :ref:`incorrectly quoted under SQLite
<sqlite-connection-queries>`.

If you are using :doc:`multiple databases</topics/db/multi-db>`, you can use the
same interface on each member of the ``connections`` dictionary::
+0 −10
Original line number Diff line number Diff line
@@ -704,16 +704,6 @@ can use the "pyformat" parameter style, where placeholders in the query
are given as ``'%(name)s'`` and the parameters are passed as a dictionary
rather than a list. SQLite does not support this.

.. _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 an SQLite shell.

.. _oracle-notes:

Oracle notes
+2 −0
Original line number Diff line number Diff line
@@ -510,6 +510,8 @@ Models

* Added support for referencing annotations in ``QuerySet.distinct()``.

* ``connection.queries`` shows queries with substituted parameters on SQLite.

CSRF
^^^^

+13 −3
Original line number Diff line number Diff line
@@ -26,7 +26,6 @@ from django.test import (
    SimpleTestCase, TestCase, TransactionTestCase, mock, override_settings,
    skipIfDBFeature, skipUnlessDBFeature,
)
from django.test.utils import str_prefix
from django.utils import six
from django.utils.six.moves import range

@@ -388,8 +387,19 @@ class LastExecutedQueryTest(TestCase):
        # This shouldn't raise an exception
        query = "SELECT strftime('%Y', 'now');"
        connection.cursor().execute(query)
        self.assertEqual(connection.queries[-1]['sql'],
            str_prefix("QUERY = %(_)s\"SELECT strftime('%%Y', 'now');\" - PARAMS = ()"))
        self.assertEqual(connection.queries[-1]['sql'], query)

    @unittest.skipUnless(connection.vendor == 'sqlite',
                         "This test is specific to SQLite.")
    def test_parameter_quoting_on_sqlite(self):
        # The implementation of last_executed_queries isn't optimal. It's
        # worth testing that parameters are quoted. See #14091.
        query = "SELECT %s"
        params = ["\"'\\"]
        connection.cursor().execute(query, params)
        # Note that the single quote is repeated
        substituted = "SELECT '\"''\\'"
        self.assertEqual(connection.queries[-1]['sql'], substituted)


class ParameterHandlingTest(TestCase):
Loading