~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/registry/vocabularies.py

  • Committer: Ian Booth
  • Date: 2011-06-02 00:34:24 UTC
  • mto: This revision was merged to the branch mainline in revision 13155.
  • Revision ID: ian.booth@canonical.com-20110602003424-lcwp4qviwn1n77mw
Fix tests and put new person picker results ordering behind feature flag

Show diffs side-by-side

added added

removed removed

Lines of Context:
525
525
 
526
526
    def _doSearch(self, text=""):
527
527
        """Return the people/teams whose fti or email address match :text:"""
 
528
        if self.enhanced_picker_enabled:
 
529
            return self._doSearchWithImprovedSorting(text)
 
530
        else:
 
531
            return self._doSearchWithOriginalSorting(text)
 
532
 
 
533
    def _doSearchWithOriginalSorting(self, text=""):
 
534
        private_query, private_tables = self._privateTeamQueryAndTables()
 
535
        exact_match = None
 
536
 
 
537
        # Short circuit if there is no search text - all valid people and
 
538
        # teams have been requested.
 
539
        if not text:
 
540
            tables = [
 
541
                Person,
 
542
                Join(self.cache_table_name,
 
543
                     SQL("%s.id = Person.id" % self.cache_table_name)),
 
544
                ]
 
545
            tables.extend(private_tables)
 
546
            result = self.store.using(*tables).find(
 
547
                Person,
 
548
                And(
 
549
                    Or(Person.visibility == PersonVisibility.PUBLIC,
 
550
                       private_query,
 
551
                       ),
 
552
                    Person.merged == None,
 
553
                    self.extra_clause
 
554
                    )
 
555
                )
 
556
        else:
 
557
            # Do a full search based on the text given.
 
558
 
 
559
            # The queries are broken up into several steps for efficiency.
 
560
            # The public person and team searches do not need to join with the
 
561
            # TeamParticipation table, which is very expensive.  The search
 
562
            # for private teams does need that table but the number of private
 
563
            # teams is very small so the cost is not great.
 
564
 
 
565
            # First search for public persons and teams that match the text.
 
566
            public_tables = [
 
567
                Person,
 
568
                LeftJoin(EmailAddress, EmailAddress.person == Person.id),
 
569
                ]
 
570
 
 
571
            # Create an inner query that will match public persons and teams
 
572
            # that have the search text in the fti, at the start of the email
 
573
            # address, or as their full IRC nickname.
 
574
            # Since we may be eliminating results with the limit to improve
 
575
            # performance, we sort by the rank, so that we will always get
 
576
            # the best results. The fti rank will be between 0 and 1.
 
577
            # Note we use lower() instead of the non-standard ILIKE because
 
578
            # ILIKE doesn't hit the indexes.
 
579
            # The '%%' is necessary because storm variable substitution
 
580
            # converts it to '%'.
 
581
            public_inner_textual_select = SQL("""
 
582
                SELECT id FROM (
 
583
                    SELECT Person.id, 100 AS rank
 
584
                    FROM Person
 
585
                    WHERE name = ?
 
586
                    UNION ALL
 
587
                    SELECT Person.id, rank(fti, ftq(?))
 
588
                    FROM Person
 
589
                    WHERE Person.fti @@ ftq(?)
 
590
                    UNION ALL
 
591
                    SELECT Person.id, 10 AS rank
 
592
                    FROM Person, IrcId
 
593
                    WHERE IrcId.person = Person.id
 
594
                        AND lower(IrcId.nickname) = ?
 
595
                    UNION ALL
 
596
                    SELECT Person.id, 1 AS rank
 
597
                    FROM Person, EmailAddress
 
598
                    WHERE EmailAddress.person = Person.id
 
599
                        AND lower(email) LIKE ? || '%%'
 
600
                        AND EmailAddress.status IN (?, ?)
 
601
                    ) AS public_subquery
 
602
                ORDER BY rank DESC
 
603
                LIMIT ?
 
604
                """, (text, text, text, text, text,
 
605
                      EmailAddressStatus.VALIDATED.value,
 
606
                      EmailAddressStatus.PREFERRED.value,
 
607
                      self.LIMIT))
 
608
 
 
609
            public_result = self.store.using(*public_tables).find(
 
610
                Person,
 
611
                And(
 
612
                    Person.id.is_in(public_inner_textual_select),
 
613
                    Person.visibility == PersonVisibility.PUBLIC,
 
614
                    Person.merged == None,
 
615
                    Or(# A valid person-or-team is either a team...
 
616
                       # Note: 'Not' due to Bug 244768.
 
617
                       Not(Person.teamowner == None),
 
618
                       # Or a person who has a preferred email address.
 
619
                       EmailAddress.status == EmailAddressStatus.PREFERRED),
 
620
                    ))
 
621
            # The public query doesn't need to be ordered as it will be done
 
622
            # at the end.
 
623
            public_result.order_by()
 
624
 
 
625
            # Next search for the private teams.
 
626
            private_query, private_tables = self._privateTeamQueryAndTables()
 
627
            private_tables = [Person] + private_tables
 
628
 
 
629
            # Searching for private teams that match can be easier since we
 
630
            # are only interested in teams.  Teams can have email addresses
 
631
            # but we're electing to ignore them here.
 
632
            private_result = self.store.using(*private_tables).find(
 
633
                Person,
 
634
                And(
 
635
                    SQL('Person.fti @@ ftq(?)', [text]),
 
636
                    private_query,
 
637
                    )
 
638
                )
 
639
 
 
640
            private_result.order_by(SQL('rank(fti, ftq(?)) DESC', [text]))
 
641
            private_result.config(limit=self.LIMIT)
 
642
 
 
643
            combined_result = public_result.union(private_result)
 
644
            # Eliminate default ordering.
 
645
            combined_result.order_by()
 
646
            # XXX: BradCrittenden 2009-04-26 bug=217644: The use of Alias and
 
647
            # _get_select() is a work-around for .count() not working
 
648
            # with the 'distinct' option.
 
649
            subselect = Alias(combined_result._get_select(), 'Person')
 
650
            exact_match = (Person.name == text)
 
651
            result = self.store.using(subselect).find(
 
652
                (Person, exact_match),
 
653
                self.extra_clause)
 
654
        # XXX: BradCrittenden 2009-05-07 bug=373228: A bug in Storm prevents
 
655
        # setting the 'distinct' and 'limit' options in a single call to
 
656
        # .config().  The work-around is to split them up.  Note the limit has
 
657
        # to be after the call to 'order_by' for this work-around to be
 
658
        # effective.
 
659
        result.config(distinct=True)
 
660
        if exact_match is not None:
 
661
            # A DISTINCT requires that the sort parameters appear in the
 
662
            # select, but it will break the vocabulary if it returns a list of
 
663
            # tuples instead of a list of Person objects, so we create
 
664
            # another subselect to sort after the DISTINCT is done.
 
665
            distinct_subselect = Alias(result._get_select(), 'Person')
 
666
            result = self.store.using(distinct_subselect).find(Person)
 
667
            result.order_by(
 
668
                Desc(exact_match), Person.displayname, Person.name)
 
669
        else:
 
670
            result.order_by(Person.displayname, Person.name)
 
671
        result.config(limit=self.LIMIT)
 
672
        return result
 
673
 
 
674
    def _doSearchWithImprovedSorting(self, text=""):
 
675
        """Return the people/teams whose fti or email address match :text:"""
528
676
 
529
677
        private_query, private_tables = self._privateTeamQueryAndTables()
530
678
 
751
899
                        self.extra_clause)
752
900
            result = self.store.using(*tables).find(Person, query)
753
901
        else:
754
 
            name_match_query = SQL("""
755
 
                lower(Person.name) LIKE ? || '%%'
756
 
                OR lower(Person.displayname) LIKE ? || '%%'
757
 
                OR Person.fti @@ ftq(?)
758
 
                """, [text, text, text]),
 
902
            if self.enhanced_picker_enabled:
 
903
                name_match_query = SQL("""
 
904
                    lower(Person.name) LIKE ? || '%%'
 
905
                    OR lower(Person.displayname) LIKE ? || '%%'
 
906
                    OR Person.fti @@ ftq(?)
 
907
                    """, [text, text, text]),
 
908
            else:
 
909
                name_match_query = SQL("Person.fti @@ ftq(%s)" % quote(text))
759
910
 
760
911
            email_storm_query = self.store.find(
761
912
                EmailAddress.personID,