657
670
result.order_by(Person.displayname, Person.name)
658
671
result.config(limit=self.LIMIT)
660
# We will be displaying the person's irc nic(s) in the description
661
# so we need to bulk load them in one query for performance.
674
def _doSearchWithImprovedSorting(self, text=""):
675
"""Return the people/teams whose fti or email address match :text:"""
677
private_query, private_tables = self._privateTeamQueryAndTables()
679
# Short circuit if there is no search text - all valid people and
680
# teams have been requested.
684
Join(self.cache_table_name,
685
SQL("%s.id = Person.id" % self.cache_table_name)),
687
tables.extend(private_tables)
688
result = self.store.using(*tables).find(
691
Or(Person.visibility == PersonVisibility.PUBLIC,
694
Person.merged == None,
698
result.config(distinct=True)
699
result.order_by(Person.displayname, Person.name)
701
# Do a full search based on the text given.
703
# The queries are broken up into several steps for efficiency.
704
# The public person and team searches do not need to join with the
705
# TeamParticipation table, which is very expensive. The search
706
# for private teams does need that table but the number of private
707
# teams is very small so the cost is not great. However, if the
708
# person is a logged in administrator, we don't need to join to
709
# the TeamParticipation table and can construct a more efficient
710
# query (since in this case we are searching all private teams).
712
# Create a query that will match public persons and teams that
713
# have the search text in the fti, at the start of their email
714
# address, as their full IRC nickname, or at the start of their
716
# Since we may be eliminating results with the limit to improve
717
# performance, we sort by the rank, so that we will always get
718
# the best results. The fti rank will be between 0 and 1.
719
# Note we use lower() instead of the non-standard ILIKE because
720
# ILIKE doesn't hit the indexes.
721
# The '%%' is necessary because storm variable substitution
722
# converts it to '%'.
724
# This is the SQL that will give us the IDs of the people we want
726
matching_person_sql = SQL("""
727
SELECT id, MAX(rank) AS rank, false as is_private_team
731
when person.name=? then 100
732
when lower(person.name) like ? || '%%' then 75
733
when lower(person.displayname) like ? || '%%' then 50
734
else rank(fti, ftq(?))
737
WHERE lower(Person.name) LIKE ? || '%%'
738
or lower(Person.displayname) LIKE ? || '%%'
739
or Person.fti @@ ftq(?)
741
SELECT Person.id, 25 AS rank
743
WHERE Person.id = IrcID.person
744
AND IrcID.nickname = ?
746
SELECT Person.id, 10 AS rank
747
FROM Person, EmailAddress
748
WHERE Person.id = EmailAddress.person
749
AND LOWER(EmailAddress.email) LIKE ? || '%%'
752
GROUP BY id, is_private_team
753
""", (text, text, text, text, text, text, text, text, text,
754
EmailAddressStatus.VALIDATED.value,
755
EmailAddressStatus.PREFERRED.value))
757
# Do we need to search for private teams.
759
private_tables = [Person] + private_tables
760
private_ranking_sql = SQL("""
762
when person.name=? then 100
763
when lower(person.name) like ? || '%%' then 75
764
when lower(person.displayname) like ? || '%%' then 50
765
else rank(fti, ftq(?))
767
""", (text, text, text, text))
769
# Searching for private teams that match can be easier since
770
# we are only interested in teams. Teams can have email
771
# addresses but we're electing to ignore them here.
772
private_result_select = Select(
773
tables=private_tables,
774
columns=(Person.id, private_ranking_sql,
775
SQL("true as is_private_team")),
778
lower(Person.name) LIKE ? || '%%'
779
OR lower(Person.displayname) LIKE ? || '%%'
780
OR Person.fti @@ ftq(?)
781
""", [text, text, text]),
783
matching_person_sql = Union(matching_person_sql,
784
private_result_select, all=True)
786
# The tables for public persons and teams that match the text.
788
SQL("MatchingPerson"),
790
LeftJoin(EmailAddress, EmailAddress.person == Person.id),
793
# If private_tables is empty, we are searching for all private
794
# teams. We can simply append the private query component to the
795
# public query. Otherwise, for efficiency as stated earlier, we
796
# need to do a separate query to join to the TeamParticipation
798
private_teams_query = private_query
800
private_teams_query = SQL("is_private_team")
802
# We just select the required ids since we will use
803
# IPersonSet.getPrecachedPersonsFromIDs to load the results
804
matching_with = With("MatchingPerson", matching_person_sql)
805
result = self.store.with_(
806
matching_with).using(*public_tables).find(
809
SQL("Person.id = MatchingPerson.id"),
811
And(# A public person or team
812
Person.visibility == PersonVisibility.PUBLIC,
813
Person.merged == None,
814
Or(# A valid person-or-team is either a team...
815
# Note: 'Not' due to Bug 244768.
816
Not(Person.teamowner == None),
817
# Or a person who has preferred email address.
818
EmailAddress.status ==
819
EmailAddressStatus.PREFERRED)),
821
private_teams_query),
824
# Better ranked matches go first.
826
SQL("rank desc"), Person.displayname, Person.name)
827
result.config(limit=self.LIMIT)
829
# We will be displaying the person's irc nick(s) and emails in the
830
# description so we need to bulk load them for performance, otherwise
831
# we get one query per person per attribute.
662
832
def pre_iter_hook(rows):
663
833
persons = set(obj for obj in rows)
664
bulk.load_referencing(IrcID, persons, ['personID'])
835
emails = bulk.load_referencing(
836
EmailAddress, persons, ['personID'])
837
email_by_person = dict((email.personID, email)
839
if email.status == EmailAddressStatus.PREFERRED)
842
nicks = bulk.load_referencing(IrcID, persons, ['personID'])
843
nicks_by_person = dict((nick.personID, nicks)
846
for person in persons:
847
cache = get_property_cache(person)
848
cache.preferredemail = email_by_person.get(person.id, None)
849
cache.ircnicknames = nicks_by_person.get(person.id, None)
666
851
return DecoratedResultSet(result, pre_iter_hook=pre_iter_hook)