#34894: Query.change_aliases() has several significant bugs
-------------------------------------+-------------------------------------
Reporter: Aqua Penguin | Owner: nobody
Type: Bug | Status: new
Component: Database layer | Version: 4.2
(models, ORM) |
Severity: Normal | Resolution:
Keywords: Query | Triage Stage:
change_aliases | Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Aqua Penguin):
Here the attached patch.
{{{
#!diff
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index a7839ccb4d..95458a9102 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -988,14 +988,27 @@ class Query(BaseExpression):
}
# 2. Rename the alias in the internal table/alias datastructures.
- for old_alias, new_alias in change_map.items():
- if old_alias not in self.alias_map:
+ # - The order of insertion in alias_map has great importance for
+ # future iterations, so we need to rebuild it in the original
order.
+ # - alias_data must be relabeled if it contains a change to do in
+ # alias_data.parent_alias, even if its alias does not change.
+ # - Cached property base_table need to change if necessary.
+ base_table_cached = self.__dict__.get("base_table", None)
+ old_alias_map, self.alias_map = self.alias_map, {}
+ for old_alias, alias_data in old_alias_map.items():
+ if old_alias not in change_map:
+ self.alias_map[old_alias] = (
+ alias_data.relabeled_clone(change_map)
+ if alias_data.parent_alias in change_map
+ else alias_data
+ )
continue
- alias_data =
self.alias_map[old_alias].relabeled_clone(change_map)
- self.alias_map[new_alias] = alias_data
+ new_alias = change_map[old_alias]
+ if old_alias == base_table_cached:
+ self.__dict__["base_table"] = new_alias
+ self.alias_map[new_alias] =
alias_data.relabeled_clone(change_map)
self.alias_refcount[new_alias] =
self.alias_refcount[old_alias]
del self.alias_refcount[old_alias]
- del self.alias_map[old_alias]
table_aliases = self.table_map[alias_data.table_name]
for pos, alias in enumerate(table_aliases):
diff --git a/tests/queries/test_query.py b/tests/queries/test_query.py
index 99d0e32427..f5a17c56e5 100644
--- a/tests/queries/test_query.py
+++ b/tests/queries/test_query.py
@@ -89,6 +89,45 @@ class TestQuery(SimpleTestCase):
self.assertIsNone(lookup.lhs.lhs.alias)
self.assertEqual(lookup.lhs.lhs.target,
Author._meta.get_field("name"))
+ def test_change_aliases_alias_map_order(self):
+ query = Query(Item)
+ # force to create a join between Item and Author
+ query.add_q(Q(creator__num__gt=2))
+ # force to initialize all joins and caches
+ sql = str(query)
+ order = [
+ "item_alias" if alias == Item._meta.db_table else alias
+ for alias in list(query.alias_map)
+ ]
+ query.change_aliases({
+ Item._meta.db_table: "item_alias",
+ })
+ self.assertEqual(list(query.alias_map), order)
+
+ def test_change_aliases_nested_joins(self):
+ query = Query(Item)
+ # force to create a join between Item and Author
+ query.add_q(Q(creator__num__gt=2))
+ # force to initialize all joins and caches
+ sql = str(query)
+
self.assertEqual(query.alias_map[Author._meta.db_table].parent_alias,
Item._meta.db_table)
+ query.change_aliases({
+ Item._meta.db_table: "item_alias",
+ })
+
self.assertEqual(query.alias_map[Author._meta.db_table].parent_alias,
"item_alias")
+
+ def test_change_aliases_base_table(self):
+ query = Query(Item)
+ # force to create a join between Item and Author
+ query.add_q(Q(creator__num__gt=2))
+ # force to initialize all joins and caches
+ sql = str(query)
+ self.assertEqual(query.base_table, Item._meta.db_table)
+ query.change_aliases({
+ Item._meta.db_table: "item_alias",
+ })
+ self.assertEqual(query.base_table, "item_alias")
+
def test_negated_nullable(self):
query = Query(Item)
where = query.build_where(~Q(modified__lt=datetime(2017, 1, 1)))
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/34894#comment:2>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/0107018b132efc59-b37abb72-14c9-40ac-8446-b0a970772f0b-000000%40eu-central-1.amazonses.com.