From 415f50298f97fb17f841a9df38d995ccf347dfcc Mon Sep 17 00:00:00 2001 From: Alexander Lyabah Date: Sat, 28 Nov 2020 18:08:27 +0200 Subject: [PATCH] Fixed #32231 -- Allowed passing None params to QuerySet.raw(). --- django/db/models/query.py | 6 +++--- django/db/models/sql/query.py | 10 ++++++++-- docs/ref/models/querysets.txt | 7 ++++++- docs/topics/db/sql.txt | 7 ++++++- tests/raw_query/tests.py | 5 +++++ 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index 9dc98c02d1b..32fee78e9f1 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -818,7 +818,7 @@ class QuerySet: # 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: using = self.db 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 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): self.raw_query = raw_query self.model = model self._db = using self._hints = hints or {} 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._result_cache = None self._prefetch_related_lookups = () diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 93cc32ac3cc..a39430b28db 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -69,8 +69,8 @@ JoinInfo = namedtuple( class RawQuery: """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.using = using self.cursor = None @@ -111,9 +111,13 @@ class RawQuery: @property def params_type(self): + if self.params is None: + return None return dict if isinstance(self.params, Mapping) else tuple def __str__(self): + if self.params_type is None: + return self.sql return self.sql % self.params_type(self.params) def _execute_query(self): @@ -127,6 +131,8 @@ class RawQuery: params = tuple(adapter(val) for val in self.params) elif params_type is dict: params = {key: adapter(val) for key, val in self.params.items()} + elif params_type is None: + params = None else: raise RuntimeError("Unexpected params type: %s" % params_type) diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 70a69893066..2c8c2e38fdd 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -1843,7 +1843,7 @@ raised if ``select_for_update()`` is used in autocommit mode. ``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 ``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 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 ---------------------------------------- diff --git a/docs/topics/db/sql.txt b/docs/topics/db/sql.txt index c7224c78b0b..fe7b0a4bd83 100644 --- a/docs/topics/db/sql.txt +++ b/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 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 ``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 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 ------------------------------------ diff --git a/tests/raw_query/tests.py b/tests/raw_query/tests.py index 17a5a68a8af..cf57cfd696e 100644 --- a/tests/raw_query/tests.py +++ b/tests/raw_query/tests.py @@ -180,6 +180,11 @@ class RawQueryTests(TestCase): self.assertEqual(len(results), 1) 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): query = "SELECT * FROM raw_query_author WHERE first_name like 'J%%'" qset = Author.objects.raw(query)