#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.

Reply via email to