Merge the 'rhs' query into the current one (with any 'rhs' effects being applied *after* (that is, "to the right of") anything in the current query. 'rhs' is not modified during a call to this function. The 'connector' parameter describes how to connect filters from
(self, rhs, connector)
| 698 | return "\n".join(compiler.explain_query()) |
| 699 | |
| 700 | def combine(self, rhs, connector): |
| 701 | """ |
| 702 | Merge the 'rhs' query into the current one (with any 'rhs' effects |
| 703 | being applied *after* (that is, "to the right of") anything in the |
| 704 | current query. 'rhs' is not modified during a call to this function. |
| 705 | |
| 706 | The 'connector' parameter describes how to connect filters from the |
| 707 | 'rhs' query. |
| 708 | """ |
| 709 | if self.model != rhs.model: |
| 710 | raise TypeError("Cannot combine queries on two different base models.") |
| 711 | if self.is_sliced: |
| 712 | raise TypeError("Cannot combine queries once a slice has been taken.") |
| 713 | if self.distinct != rhs.distinct: |
| 714 | raise TypeError("Cannot combine a unique query with a non-unique query.") |
| 715 | if self.distinct_fields != rhs.distinct_fields: |
| 716 | raise TypeError("Cannot combine queries with different distinct fields.") |
| 717 | |
| 718 | # If lhs and rhs shares the same alias prefix, it is possible to have |
| 719 | # conflicting alias changes like T4 -> T5, T5 -> T6, which might end up |
| 720 | # as T4 -> T6 while combining two querysets. To prevent this, change an |
| 721 | # alias prefix of the rhs and update current aliases accordingly, |
| 722 | # except if the alias is the base table since it must be present in the |
| 723 | # query on both sides. |
| 724 | initial_alias = self.get_initial_alias() |
| 725 | rhs = rhs.clone() |
| 726 | rhs.bump_prefix(self, exclude={initial_alias}) |
| 727 | |
| 728 | # Work out how to relabel the rhs aliases, if necessary. |
| 729 | change_map = {} |
| 730 | conjunction = connector == AND |
| 731 | |
| 732 | # Determine which existing joins can be reused. When combining the |
| 733 | # query with AND we must recreate all joins for m2m filters. When |
| 734 | # combining with OR we can reuse joins. The reason is that in AND |
| 735 | # case a single row can't fulfill a condition like: |
| 736 | # revrel__col=1 & revrel__col=2 |
| 737 | # But, there might be two different related rows matching this |
| 738 | # condition. In OR case a single True is enough, so single row is |
| 739 | # enough, too. |
| 740 | # |
| 741 | # Note that we will be creating duplicate joins for non-m2m joins in |
| 742 | # the AND case. The results will be correct but this creates too many |
| 743 | # joins. This is something that could be fixed later on. |
| 744 | reuse = set() if conjunction else set(self.alias_map) |
| 745 | joinpromoter = JoinPromoter(connector, 2, False) |
| 746 | joinpromoter.add_votes( |
| 747 | j for j in self.alias_map if self.alias_map[j].join_type == INNER |
| 748 | ) |
| 749 | rhs_votes = set() |
| 750 | # Now, add the joins from rhs query into the new query (skipping base |
| 751 | # table). |
| 752 | rhs_tables = list(rhs.alias_map)[1:] |
| 753 | for alias in rhs_tables: |
| 754 | join = rhs.alias_map[alias] |
| 755 | # If the left side of the join was already relabeled, use the |
| 756 | # updated alias. |
| 757 | join = join.relabeled_clone(change_map) |