Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #25947 -- Query's str() method worked normally when 'default' database is empty #18039

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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