diff --git a/resources/sql/patches/078.nametoken.sql b/resources/sql/patches/078.nametoken.sql new file mode 100644 index 0000000000..aa83714aa8 --- /dev/null +++ b/resources/sql/patches/078.nametoken.sql @@ -0,0 +1,6 @@ +CREATE TABLE phabricator_user.user_nametoken ( + token VARCHAR(255) NOT NULL, + userID INT UNSIGNED NOT NULL, + KEY (token), + key (userID) +) ENGINE=InnoDB; diff --git a/resources/sql/patches/079.nametokenindex.php b/resources/sql/patches/079.nametokenindex.php new file mode 100644 index 0000000000..a6aca0189a --- /dev/null +++ b/resources/sql/patches/079.nametokenindex.php @@ -0,0 +1,30 @@ +loadAll(); +echo count($users)." users to index"; +foreach ($users as $user) { + $user->updateNameTokens(); + echo "."; +} + +echo "\nDone.\n"; diff --git a/src/applications/people/storage/user/PhabricatorUser.php b/src/applications/people/storage/user/PhabricatorUser.php index 008b2d834c..b7313ca68a 100644 --- a/src/applications/people/storage/user/PhabricatorUser.php +++ b/src/applications/people/storage/user/PhabricatorUser.php @@ -19,6 +19,7 @@ class PhabricatorUser extends PhabricatorUserDAO { const SESSION_TABLE = 'phabricator_session'; + const NAMETOKEN_TABLE = 'user_nametoken'; protected $phid; protected $userName; @@ -91,6 +92,7 @@ class PhabricatorUser extends PhabricatorUserDAO { } $result = parent::save(); + $this->updateNameTokens(); PhabricatorSearchUserIndexer::indexUser($this); return $result; @@ -349,4 +351,53 @@ class PhabricatorUser extends PhabricatorUserDAO { return $preferences; } + private static function tokenizeName($name) { + if (function_exists('mb_strtolower')) { + $name = mb_strtolower($name, 'UTF-8'); + } else { + $name = strtolower($name); + } + $name = trim($name); + if (!strlen($name)) { + return array(); + } + return preg_split('/\s+/', $name); + } + + /** + * Populate the nametoken table, which used to fetch typeahead results. When + * a user types "linc", we want to match "Abraham Lincoln" from on-demand + * typeahead sources. To do this, we need a separate table of name fragments. + */ + public function updateNameTokens() { + $tokens = array_merge( + self::tokenizeName($this->getRealName()), + self::tokenizeName($this->getUserName())); + $tokens = array_unique($tokens); + $table = self::NAMETOKEN_TABLE; + $conn_w = $this->establishConnection('w'); + + $sql = array(); + foreach ($tokens as $token) { + $sql[] = qsprintf( + $conn_w, + '(%d, %s)', + $this->getID(), + $token); + } + + queryfx( + $conn_w, + 'DELETE FROM %T WHERE userID = %d', + $table, + $this->getID()); + if ($sql) { + queryfx( + $conn_w, + 'INSERT INTO %T (userID, token) VALUES %Q', + $table, + implode(', ', $sql)); + } + } + } diff --git a/src/applications/people/storage/user/__init__.php b/src/applications/people/storage/user/__init__.php index d7fd517a2d..c93a02dfda 100644 --- a/src/applications/people/storage/user/__init__.php +++ b/src/applications/people/storage/user/__init__.php @@ -14,6 +14,7 @@ phutil_require_module('phabricator', 'applications/phid/constants'); phutil_require_module('phabricator', 'applications/phid/storage/phid'); phutil_require_module('phabricator', 'applications/search/index/indexer/user'); phutil_require_module('phabricator', 'infrastructure/env'); +phutil_require_module('phabricator', 'storage/qsprintf'); phutil_require_module('phabricator', 'storage/queryfx'); phutil_require_module('phutil', 'filesystem'); diff --git a/src/applications/typeahead/controller/common/PhabricatorTypeaheadCommonDatasourceController.php b/src/applications/typeahead/controller/common/PhabricatorTypeaheadCommonDatasourceController.php index 46e3368beb..dcc03a10ad 100644 --- a/src/applications/typeahead/controller/common/PhabricatorTypeaheadCommonDatasourceController.php +++ b/src/applications/typeahead/controller/common/PhabricatorTypeaheadCommonDatasourceController.php @@ -85,12 +85,21 @@ class PhabricatorTypeaheadCommonDatasourceController 'realName', 'phid'); if ($query) { - // TODO: We probably need to split last names here. Workaround until - // we get that up and running is to not enable server-side datasources. - $users = id(new PhabricatorUser())->loadColumnsWhere($columns, - '(userName LIKE %> OR realName LIKE %>)', - $query, + $conn_r = id(new PhabricatorUser())->establishConnection('r'); + $ids = queryfx_all( + $conn_r, + 'SELECT DISTINCT userID FROM %T WHERE token LIKE %>', + PhabricatorUser::NAMETOKEN_TABLE, $query); + $ids = ipull($ids, 'userID'); + if ($ids) { + $users = id(new PhabricatorUser())->loadColumnsWhere( + $columns, + 'id IN (%Ld)', + $ids); + } else { + $users = array(); + } } else { $users = id(new PhabricatorUser())->loadColumns($columns); } diff --git a/src/applications/typeahead/controller/common/__init__.php b/src/applications/typeahead/controller/common/__init__.php index 89e2754bf6..225eedc9f7 100644 --- a/src/applications/typeahead/controller/common/__init__.php +++ b/src/applications/typeahead/controller/common/__init__.php @@ -15,6 +15,7 @@ phutil_require_module('phabricator', 'applications/project/storage/project'); phutil_require_module('phabricator', 'applications/repository/storage/arcanistproject'); phutil_require_module('phabricator', 'applications/repository/storage/repository'); phutil_require_module('phabricator', 'applications/typeahead/controller/base'); +phutil_require_module('phabricator', 'storage/queryfx'); phutil_require_module('phutil', 'utils');