Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15384003
D16838.id40547.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
12 KB
Referenced Files
None
Subscribers
None
D16838.id40547.diff
View Options
diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php
--- a/src/applications/people/query/PhabricatorPeopleQuery.php
+++ b/src/applications/people/query/PhabricatorPeopleQuery.php
@@ -17,6 +17,7 @@
private $isApproved;
private $nameLike;
private $nameTokens;
+ private $namePrefixes;
private $needPrimaryEmail;
private $needProfile;
@@ -95,6 +96,11 @@
return $this;
}
+ public function withNamePrefixes(array $prefixes) {
+ $this->namePrefixes = $prefixes;
+ return $this;
+ }
+
public function needPrimaryEmail($need) {
$this->needPrimaryEmail = $need;
return $this;
@@ -256,6 +262,17 @@
$this->usernames);
}
+ if ($this->namePrefixes) {
+ $parts = array();
+ foreach ($this->namePrefixes as $name_prefix) {
+ $parts[] = qsprintf(
+ $conn,
+ 'user.username LIKE %>',
+ $name_prefix);
+ }
+ $where[] = '('.implode(' OR ', $parts).')';
+ }
+
if ($this->emails !== null) {
$where[] = qsprintf(
$conn,
diff --git a/src/applications/people/typeahead/PhabricatorPeopleDatasource.php b/src/applications/people/typeahead/PhabricatorPeopleDatasource.php
--- a/src/applications/people/typeahead/PhabricatorPeopleDatasource.php
+++ b/src/applications/people/typeahead/PhabricatorPeopleDatasource.php
@@ -17,13 +17,18 @@
public function loadResults() {
$viewer = $this->getViewer();
- $tokens = $this->getTokens();
$query = id(new PhabricatorPeopleQuery())
->setOrderVector(array('username'));
- if ($tokens) {
- $query->withNameTokens($tokens);
+ if ($this->getPhase() == self::PHASE_PREFIX) {
+ $prefix = $this->getPrefixQuery();
+ $query->withNamePrefixes(array($prefix));
+ } else {
+ $tokens = $this->getTokens();
+ if ($tokens) {
+ $query->withNameTokens($tokens);
+ }
}
$users = $this->executeQuery($query);
diff --git a/src/applications/project/query/PhabricatorProjectQuery.php b/src/applications/project/query/PhabricatorProjectQuery.php
--- a/src/applications/project/query/PhabricatorProjectQuery.php
+++ b/src/applications/project/query/PhabricatorProjectQuery.php
@@ -12,6 +12,7 @@
private $slugMap;
private $allSlugs;
private $names;
+ private $namePrefixes;
private $nameTokens;
private $icons;
private $colors;
@@ -78,6 +79,11 @@
return $this;
}
+ public function withNamePrefixes(array $prefixes) {
+ $this->namePrefixes = $prefixes;
+ return $this;
+ }
+
public function withNameTokens(array $tokens) {
$this->nameTokens = array_values($tokens);
return $this;
@@ -464,6 +470,17 @@
$this->names);
}
+ if ($this->namePrefixes) {
+ $parts = array();
+ foreach ($this->namePrefixes as $name_prefix) {
+ $parts[] = qsprintf(
+ $conn,
+ 'name LIKE %>',
+ $name_prefix);
+ }
+ $where[] = '('.implode(' OR ', $parts).')';
+ }
+
if ($this->icons !== null) {
$where[] = qsprintf(
$conn,
diff --git a/src/applications/project/typeahead/PhabricatorProjectDatasource.php b/src/applications/project/typeahead/PhabricatorProjectDatasource.php
--- a/src/applications/project/typeahead/PhabricatorProjectDatasource.php
+++ b/src/applications/project/typeahead/PhabricatorProjectDatasource.php
@@ -28,7 +28,10 @@
->needImages(true)
->needSlugs(true);
- if ($tokens) {
+ if ($this->getPhase() == self::PHASE_PREFIX) {
+ $prefix = $this->getPrefixQuery();
+ $query->withNamePrefixes(array($prefix));
+ } else if ($tokens) {
$query->withNameTokens($tokens);
}
diff --git a/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php b/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php
--- a/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php
+++ b/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php
@@ -325,6 +325,11 @@
pht('Icon'),
pht('Closed'),
pht('Sprite'),
+ pht('Color'),
+ pht('Type'),
+ pht('Unique'),
+ pht('Auto'),
+ pht('Phase'),
));
$result_box = id(new PHUIObjectBoxView())
diff --git a/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php b/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php
--- a/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php
+++ b/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php
@@ -4,6 +4,8 @@
extends PhabricatorTypeaheadDatasource {
private $usable;
+ private $prefixString;
+ private $prefixLength;
abstract public function getComponentDatasources();
@@ -22,9 +24,51 @@
}
public function loadResults() {
+ $phases = array();
+
+ // We only need to do a prefix phase query if there's an actual query
+ // string. If the user didn't type anything, nothing can possibly match it.
+ if (strlen($this->getRawQuery())) {
+ $phases[] = self::PHASE_PREFIX;
+ }
+
+ $phases[] = self::PHASE_CONTENT;
+
$offset = $this->getOffset();
$limit = $this->getLimit();
+ $results = array();
+ foreach ($phases as $phase) {
+ if ($limit) {
+ $phase_limit = ($offset + $limit) - count($results);
+ } else {
+ $phase_limit = 0;
+ }
+
+ $phase_results = $this->loadResultsForPhase(
+ $phase,
+ $phase_limit);
+
+ foreach ($phase_results as $result) {
+ $results[] = $result;
+ }
+
+ if ($limit) {
+ if (count($results) >= $offset + $limit) {
+ break;
+ }
+ }
+ }
+
+ return $results;
+ }
+
+ protected function loadResultsForPhase($phase, $limit) {
+ if ($phase == self::PHASE_PREFIX) {
+ $this->prefixString = $this->getPrefixQuery();
+ $this->prefixLength = strlen($this->prefixString);
+ }
+
// If the input query is a function like `members(platy`, and we can
// parse the function, we strip the function off and hand the stripped
// query to child sources. This makes it easier to implement function
@@ -62,28 +106,110 @@
}
$source
+ ->setPhase($phase)
->setFunctionStack($source_stack)
->setRawQuery($source_query)
->setQuery($this->getQuery())
->setViewer($this->getViewer());
- if ($limit) {
- $source->setLimit($offset + $limit);
- }
-
if ($is_browse) {
$source->setIsBrowse(true);
}
- $source_results = $source->loadResults();
- $source_results = $source->didLoadResults($source_results);
+ if ($limit) {
+ // If we are loading results from a source with a limit, it may return
+ // some results which belong to the wrong phase. We need an entire page
+ // of valid results in the correct phase AFTER any results for the
+ // wrong phase are filtered for pagination to work correctly.
+
+ // To make sure we can get there, we fetch more and more results until
+ // enough of them survive filtering to generate a full page.
+
+ // We start by fetching 150% of the results than we think we need, and
+ // double the amount we overfetch by each time.
+ $factor = 1.5;
+ while (true) {
+ $query_source = clone $source;
+ $total = (int)ceil($limit * $factor) + 1;
+ $query_source->setLimit($total);
+
+ $source_results = $query_source->loadResultsForPhase(
+ $phase,
+ $limit);
+
+ // If there are fewer unfiltered results than we asked for, we know
+ // this is the entire result set and we don't need to keep going.
+ if (count($source_results) < $total) {
+ $source_results = $query_source->didLoadResults($source_results);
+ $source_results = $this->filterPhaseResults(
+ $phase,
+ $source_results);
+ break;
+ }
+
+ // Otherwise, this result set have everything we need, or may not.
+ // Filter the results that are part of the wrong phase out first...
+ $source_results = $query_source->didLoadResults($source_results);
+ $source_results = $this->filterPhaseResults($phase, $source_results);
+
+ // Now check if we have enough results left. If we do, we're all set.
+ if (count($source_results) >= $total) {
+ break;
+ }
+
+ // We filtered out too many results to have a full page left, so we
+ // need to run the query again, asking for even more results. We'll
+ // keep doing this until we get a full page or get all of the
+ // results.
+ $factor = $factor * 2;
+ }
+ } else {
+ $source_results = $source->loadResults();
+ $source_results = $source->didLoadResults($source_results);
+ $source_results = $this->filterPhaseResults($phase, $source_results);
+ }
+
$results[] = $source_results;
}
$results = array_mergev($results);
$results = msort($results, 'getSortKey');
- $count = count($results);
+ $results = $this->sliceResults($results);
+
+ return $results;
+ }
+
+ private function filterPhaseResults($phase, $source_results) {
+ foreach ($source_results as $key => $source_result) {
+ $result_phase = $this->getResultPhase($source_result);
+
+ if ($result_phase != $phase) {
+ unset($source_results[$key]);
+ continue;
+ }
+
+ $source_result->setPhase($result_phase);
+ }
+
+ return $source_results;
+ }
+
+ private function getResultPhase(PhabricatorTypeaheadResult $result) {
+ if ($this->prefixLength) {
+ $result_name = phutil_utf8_strtolower($result->getName());
+ if (!strncmp($result_name, $this->prefixString, $this->prefixLength)) {
+ return self::PHASE_PREFIX;
+ }
+ }
+
+ return self::PHASE_CONTENT;
+ }
+
+ protected function sliceResults(array $results) {
+ $offset = $this->getOffset();
+ $limit = $this->getLimit();
+
if ($offset || $limit) {
if (!$limit) {
$limit = count($results);
diff --git a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php
--- a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php
+++ b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php
@@ -13,6 +13,10 @@
private $parameters = array();
private $functionStack = array();
private $isBrowse;
+ private $phase = self::PHASE_CONTENT;
+
+ const PHASE_PREFIX = 'prefix';
+ const PHASE_CONTENT = 'content';
public function setLimit($limit) {
$this->limit = $limit;
@@ -46,6 +50,10 @@
return $this;
}
+ public function getPrefixQuery() {
+ return phutil_utf8_strtolower($this->getRawQuery());
+ }
+
public function getRawQuery() {
return $this->rawQuery;
}
@@ -81,6 +89,15 @@
return $this->isBrowse;
}
+ public function setPhase($phase) {
+ $this->phase = $phase;
+ return $this;
+ }
+
+ public function getPhase() {
+ return $this->phase;
+ }
+
public function getDatasourceURI() {
$uri = new PhutilURI('/typeahead/class/'.get_class($this).'/');
$uri->setQueryParams($this->parameters);
@@ -106,6 +123,13 @@
abstract public function getDatasourceApplicationClass();
abstract public function loadResults();
+ protected function loadResultsForPhase($phase, $limit) {
+ // By default, sources just load all of their results in every phase and
+ // rely on filtering at a higher level to sequence phases correctly.
+ $this->setLimit($limit);
+ return $this->loadResults();
+ }
+
protected function didLoadResults(array $results) {
return $results;
}
diff --git a/src/applications/typeahead/storage/PhabricatorTypeaheadResult.php b/src/applications/typeahead/storage/PhabricatorTypeaheadResult.php
--- a/src/applications/typeahead/storage/PhabricatorTypeaheadResult.php
+++ b/src/applications/typeahead/storage/PhabricatorTypeaheadResult.php
@@ -18,6 +18,7 @@
private $unique;
private $autocomplete;
private $attributes = array();
+ private $phase;
public function setIcon($icon) {
$this->icon = $icon;
@@ -154,6 +155,7 @@
$this->tokenType,
$this->unique ? 1 : null,
$this->autocomplete,
+ $this->phase,
);
while (end($data) === null) {
array_pop($data);
@@ -211,4 +213,13 @@
return $this;
}
+ public function setPhase($phase) {
+ $this->phase = $phase;
+ return $this;
+ }
+
+ public function getPhase() {
+ return $this->phase;
+ }
+
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Mar 15, 6:31 PM (1 w, 3 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7701076
Default Alt Text
D16838.id40547.diff (12 KB)
Attached To
Mode
D16838: Separate sever-side typeahead queries into "prefix" and "content" phases
Attached
Detach File
Event Timeline
Log In to Comment