Browse Source

Fixed #32231 -- Allowed passing None params to QuerySet.raw().

Alexander Lyabah 4 years ago
parent
commit
415f50298f

+ 3 - 3
django/db/models/query.py

@@ -818,7 +818,7 @@ class QuerySet:
     # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS #
     # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS #
     ##################################################
     ##################################################
 
 
-    def raw(self, raw_query, params=None, translations=None, using=None):
+    def raw(self, raw_query, params=(), translations=None, using=None):
         if using is None:
         if using is None:
             using = self.db
             using = self.db
         qs = RawQuerySet(raw_query, model=self.model, params=params, translations=translations, using=using)
         qs = RawQuerySet(raw_query, model=self.model, params=params, translations=translations, using=using)
@@ -1419,14 +1419,14 @@ class RawQuerySet:
     Provide an iterator which converts the results of raw SQL queries into
     Provide an iterator which converts the results of raw SQL queries into
     annotated model instances.
     annotated model instances.
     """
     """
-    def __init__(self, raw_query, model=None, query=None, params=None,
+    def __init__(self, raw_query, model=None, query=None, params=(),
                  translations=None, using=None, hints=None):
                  translations=None, using=None, hints=None):
         self.raw_query = raw_query
         self.raw_query = raw_query
         self.model = model
         self.model = model
         self._db = using
         self._db = using
         self._hints = hints or {}
         self._hints = hints or {}
         self.query = query or sql.RawQuery(sql=raw_query, using=self.db, params=params)
         self.query = query or sql.RawQuery(sql=raw_query, using=self.db, params=params)
-        self.params = params or ()
+        self.params = params
         self.translations = translations or {}
         self.translations = translations or {}
         self._result_cache = None
         self._result_cache = None
         self._prefetch_related_lookups = ()
         self._prefetch_related_lookups = ()

+ 8 - 2
django/db/models/sql/query.py

@@ -69,8 +69,8 @@ JoinInfo = namedtuple(
 class RawQuery:
 class RawQuery:
     """A single raw SQL query."""
     """A single raw SQL query."""
 
 
-    def __init__(self, sql, using, params=None):
-        self.params = params or ()
+    def __init__(self, sql, using, params=()):
+        self.params = params
         self.sql = sql
         self.sql = sql
         self.using = using
         self.using = using
         self.cursor = None
         self.cursor = None
@@ -111,9 +111,13 @@ class RawQuery:
 
 
     @property
     @property
     def params_type(self):
     def params_type(self):
+        if self.params is None:
+            return None
         return dict if isinstance(self.params, Mapping) else tuple
         return dict if isinstance(self.params, Mapping) else tuple
 
 
     def __str__(self):
     def __str__(self):
+        if self.params_type is None:
+            return self.sql
         return self.sql % self.params_type(self.params)
         return self.sql % self.params_type(self.params)
 
 
     def _execute_query(self):
     def _execute_query(self):
@@ -127,6 +131,8 @@ class RawQuery:
             params = tuple(adapter(val) for val in self.params)
             params = tuple(adapter(val) for val in self.params)
         elif params_type is dict:
         elif params_type is dict:
             params = {key: adapter(val) for key, val in self.params.items()}
             params = {key: adapter(val) for key, val in self.params.items()}
+        elif params_type is None:
+            params = None
         else:
         else:
             raise RuntimeError("Unexpected params type: %s" % params_type)
             raise RuntimeError("Unexpected params type: %s" % params_type)
 
 

+ 6 - 1
docs/ref/models/querysets.txt

@@ -1843,7 +1843,7 @@ raised if ``select_for_update()`` is used in autocommit mode.
 ``raw()``
 ``raw()``
 ~~~~~~~~~
 ~~~~~~~~~
 
 
-.. method:: raw(raw_query, params=None, translations=None)
+.. method:: raw(raw_query, params=(), translations=None)
 
 
 Takes a raw SQL query, executes it, and returns a
 Takes a raw SQL query, executes it, and returns a
 ``django.db.models.query.RawQuerySet`` instance. This ``RawQuerySet`` instance
 ``django.db.models.query.RawQuerySet`` instance. This ``RawQuerySet`` instance
@@ -1858,6 +1858,11 @@ See the :doc:`/topics/db/sql` for more information.
   filtering. As such, it should generally be called from the ``Manager`` or
   filtering. As such, it should generally be called from the ``Manager`` or
   from a fresh ``QuerySet`` instance.
   from a fresh ``QuerySet`` instance.
 
 
+.. versionchanged:: 3.2
+
+    The default value of the ``params`` argument was changed from ``None`` to
+    an empty tuple.
+
 Operators that return new ``QuerySet``\s
 Operators that return new ``QuerySet``\s
 ----------------------------------------
 ----------------------------------------
 
 

+ 6 - 1
docs/topics/db/sql.txt

@@ -43,7 +43,7 @@ Performing raw queries
 The ``raw()`` manager method can be used to perform raw SQL queries that
 The ``raw()`` manager method can be used to perform raw SQL queries that
 return model instances:
 return model instances:
 
 
-.. method:: Manager.raw(raw_query, params=None, translations=None)
+.. method:: Manager.raw(raw_query, params=(), translations=None)
 
 
 This method takes a raw SQL query, executes it, and returns a
 This method takes a raw SQL query, executes it, and returns a
 ``django.db.models.query.RawQuerySet`` instance. This ``RawQuerySet`` instance
 ``django.db.models.query.RawQuerySet`` instance. This ``RawQuerySet`` instance
@@ -99,6 +99,11 @@ make it very powerful.
     both rows will match. To prevent this, perform the correct typecasting
     both rows will match. To prevent this, perform the correct typecasting
     before using the value in a query.
     before using the value in a query.
 
 
+.. versionchanged:: 3.2
+
+    The default value of the ``params`` argument was changed from ``None`` to
+    an empty tuple.
+
 Mapping query fields to model fields
 Mapping query fields to model fields
 ------------------------------------
 ------------------------------------
 
 

+ 5 - 0
tests/raw_query/tests.py

@@ -180,6 +180,11 @@ class RawQueryTests(TestCase):
         self.assertEqual(len(results), 1)
         self.assertEqual(len(results), 1)
         self.assertIsInstance(repr(qset), str)
         self.assertIsInstance(repr(qset), str)
 
 
+    def test_params_none(self):
+        query = "SELECT * FROM raw_query_author WHERE first_name like 'J%'"
+        qset = Author.objects.raw(query, params=None)
+        self.assertEqual(len(qset), 2)
+
     def test_escaped_percent(self):
     def test_escaped_percent(self):
         query = "SELECT * FROM raw_query_author WHERE first_name like 'J%%'"
         query = "SELECT * FROM raw_query_author WHERE first_name like 'J%%'"
         qset = Author.objects.raw(query)
         qset = Author.objects.raw(query)