Skip to content

Commit

Permalink
Fixed #25947 -- Query's str() method worked normally when 'default' d…
Browse files Browse the repository at this point in the history
…atabase is empty.

`sql_with_params`, `build_lookup` and `is_nullable` in `Query` would use the correct connection and generate more reasonable SQL.
  • Loading branch information
jayvynl authored and 林志文 committed Apr 2, 2024
1 parent 5f18021 commit a4eaa1e
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 12 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,7 @@ answer newbie questions, and generally made Django that much better:
lerouxb@gmail.com
Lex Berezhny <lex@damoti.com>
Liang Feng <hutuworm@gmail.com>
Lin Zhiwen <zhiwenlin1116@gmail.com>
Lily Foote
limodou
Lincoln Smith <lincoln.smith@anu.edu.au>
Expand Down
2 changes: 2 additions & 0 deletions django/db/models/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ def __init__(self, model=None, query=None, using=None, hints=None):
self._db = using
self._hints = hints or {}
self._query = query or sql.Query(self.model)
self._query.queryset = self
self._result_cache = None
self._sticky_filter = False
self._for_write = False
Expand Down Expand Up @@ -1921,6 +1922,7 @@ def _clone(self):
c._known_related_objects = self._known_related_objects
c._iterable_class = self._iterable_class
c._fields = self._fields
c._query.queryset = c
return c

def _fetch_all(self):
Expand Down
32 changes: 20 additions & 12 deletions django/db/models/sql/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,9 @@ class Query(BaseExpression):

explain_info = None

def __init__(self, model, alias_cols=True):
def __init__(self, model, alias_cols=True, queryset=None):
self.model = model
self.queryset = queryset
self.alias_refcount = {}
# alias_map is the most important data structure regarding joins.
# It's used for recording which joins exist in the query and what
Expand All @@ -317,6 +318,20 @@ def __init__(self, model, alias_cols=True):

self._filtered_relations = {}

# Remove queryset from pickle result.
# Refer https://code.djangoproject.com/ticket/27159
def __getstate__(self):
state = self.__dict__.copy()
state["queryset"] = None
return state

@property
def db(self):
# Must not use ``if self.queryset``, that will trigger evaluation of queryset.
if self.queryset is not None:
return self.queryset.db
return DEFAULT_DB_ALIAS

@property
def output_field(self):
if len(self.select) == 1:
Expand Down Expand Up @@ -346,7 +361,7 @@ def sql_with_params(self):
Return the query as an SQL string and the parameters that will be
substituted into the query.
"""
return self.get_compiler(DEFAULT_DB_ALIAS).as_sql()
return self.get_compiler(self.db).as_sql()

def __deepcopy__(self, memo):
"""Limit the amount of work when a Query is deepcopied."""
Expand Down Expand Up @@ -1399,13 +1414,11 @@ def build_lookup(self, lookups, lhs, rhs):
return lhs.get_lookup("isnull")(lhs, True)

# For Oracle '' is equivalent to null. The check must be done at this
# stage because join promotion can't be done in the compiler. Using
# DEFAULT_DB_ALIAS isn't nice but it's the best that can be done here.
# A similar thing is done in is_nullable(), too.
# stage because join promotion can't be done in the compiler.
if (
lookup_name == "exact"
and lookup.rhs == ""
and connections[DEFAULT_DB_ALIAS].features.interprets_empty_strings_as_nulls
and connections[self.db].features.interprets_empty_strings_as_nulls
):
return lhs.get_lookup("isnull")(lhs, True)

Expand Down Expand Up @@ -2624,14 +2637,9 @@ def is_nullable(self, field):
nullable for those backends. In such situations field.null can be
False even if we should treat the field as nullable.
"""
# We need to use DEFAULT_DB_ALIAS here, as QuerySet does not have
# (nor should it have) knowledge of which connection is going to be
# used. The proper fix would be to defer all decisions where
# is_nullable() is needed to the compiler stage, but that is not easy
# to do currently.
return field.null or (
field.empty_strings_allowed
and connections[DEFAULT_DB_ALIAS].features.interprets_empty_strings_as_nulls
and connections[self.db].features.interprets_empty_strings_as_nulls
)


Expand Down
5 changes: 5 additions & 0 deletions tests/queries/test_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ def test_filter_non_conditional(self):
with self.assertRaisesMessage(TypeError, msg):
query.build_where(Func(output_field=CharField()))

def test_use_correct_db(self):
qs = Item.objects.using("other")
query = Query(Item, queryset=qs)
self.assertEqual(query.db, "other")


class TestQueryNoModel(TestCase):
def test_rawsql_annotation(self):
Expand Down

0 comments on commit a4eaa1e

Please sign in to comment.