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)
531
return self._doSearchWithOriginalSorting(text)
533
def _doSearchWithOriginalSorting(self, text=""):
534
private_query, private_tables = self._privateTeamQueryAndTables()
537
# Short circuit if there is no search text - all valid people and
538
# teams have been requested.
542
Join(self.cache_table_name,
543
SQL("%s.id = Person.id" % self.cache_table_name)),
545
tables.extend(private_tables)
546
result = self.store.using(*tables).find(
549
Or(Person.visibility == PersonVisibility.PUBLIC,
552
Person.merged == None,
557
# Do a full search based on the text given.
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.
565
# First search for public persons and teams that match the text.
568
LeftJoin(EmailAddress, EmailAddress.person == Person.id),
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("""
583
SELECT Person.id, 100 AS rank
587
SELECT Person.id, rank(fti, ftq(?))
589
WHERE Person.fti @@ ftq(?)
591
SELECT Person.id, 10 AS rank
593
WHERE IrcId.person = Person.id
594
AND lower(IrcId.nickname) = ?
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 (?, ?)
604
""", (text, text, text, text, text,
605
EmailAddressStatus.VALIDATED.value,
606
EmailAddressStatus.PREFERRED.value,
609
public_result = self.store.using(*public_tables).find(
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),
621
# The public query doesn't need to be ordered as it will be done
623
public_result.order_by()
625
# Next search for the private teams.
626
private_query, private_tables = self._privateTeamQueryAndTables()
627
private_tables = [Person] + private_tables
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(
635
SQL('Person.fti @@ ftq(?)', [text]),
640
private_result.order_by(SQL('rank(fti, ftq(?)) DESC', [text]))
641
private_result.config(limit=self.LIMIT)
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),
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
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)
668
Desc(exact_match), Person.displayname, Person.name)
670
result.order_by(Person.displayname, Person.name)
671
result.config(limit=self.LIMIT)
674
def _doSearchWithImprovedSorting(self, text=""):
675
"""Return the people/teams whose fti or email address match :text:"""
529
677
private_query, private_tables = self._privateTeamQueryAndTables()