Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14091894
D21131.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
16 KB
Referenced Files
None
Subscribers
None
D21131.diff
View Options
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1288,6 +1288,8 @@
'FeedPushWorker' => 'applications/feed/worker/FeedPushWorker.php',
'FeedQueryConduitAPIMethod' => 'applications/feed/conduit/FeedQueryConduitAPIMethod.php',
'FeedStoryNotificationGarbageCollector' => 'applications/notification/garbagecollector/FeedStoryNotificationGarbageCollector.php',
+ 'FerretConfigurableSearchFunction' => 'applications/search/ferret/function/FerretConfigurableSearchFunction.php',
+ 'FerretSearchFunction' => 'applications/search/ferret/function/FerretSearchFunction.php',
'FileAllocateConduitAPIMethod' => 'applications/files/conduit/FileAllocateConduitAPIMethod.php',
'FileConduitAPIMethod' => 'applications/files/conduit/FileConduitAPIMethod.php',
'FileCreateMailReceiver' => 'applications/files/mail/FileCreateMailReceiver.php',
@@ -7402,6 +7404,8 @@
'FeedPushWorker' => 'PhabricatorWorker',
'FeedQueryConduitAPIMethod' => 'FeedConduitAPIMethod',
'FeedStoryNotificationGarbageCollector' => 'PhabricatorGarbageCollector',
+ 'FerretConfigurableSearchFunction' => 'FerretSearchFunction',
+ 'FerretSearchFunction' => 'Phobject',
'FileAllocateConduitAPIMethod' => 'FileConduitAPIMethod',
'FileConduitAPIMethod' => 'ConduitAPIMethod',
'FileCreateMailReceiver' => 'PhabricatorApplicationMailReceiver',
diff --git a/src/applications/search/compiler/PhutilSearchQueryCompiler.php b/src/applications/search/compiler/PhutilSearchQueryCompiler.php
--- a/src/applications/search/compiler/PhutilSearchQueryCompiler.php
+++ b/src/applications/search/compiler/PhutilSearchQueryCompiler.php
@@ -148,7 +148,7 @@
if ($enable_functions) {
$found = false;
for ($jj = $ii; $jj < $length; $jj++) {
- if (preg_match('/^[a-zA-Z]\z/u', $query[$jj])) {
+ if (preg_match('/^[a-zA-Z-]\z/u', $query[$jj])) {
continue;
}
if ($query[$jj] == ':') {
diff --git a/src/applications/search/compiler/__tests__/PhutilSearchQueryCompilerTestCase.php b/src/applications/search/compiler/__tests__/PhutilSearchQueryCompilerTestCase.php
--- a/src/applications/search/compiler/__tests__/PhutilSearchQueryCompilerTestCase.php
+++ b/src/applications/search/compiler/__tests__/PhutilSearchQueryCompilerTestCase.php
@@ -197,6 +197,14 @@
// impossible.
'title:- title:x' => false,
'title:- title:~' => false,
+
+ 'abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ:xyz' => array(
+ array(
+ 'abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ',
+ $op_and,
+ 'xyz',
+ ),
+ ),
);
$this->assertCompileFunctionQueries($function_tests);
diff --git a/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php b/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php
--- a/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php
+++ b/src/applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php
@@ -253,4 +253,24 @@
$old_id);
}
+ public function newFerretSearchFunctions() {
+ return array(
+ id(new FerretConfigurableSearchFunction())
+ ->setFerretFunctionName('all')
+ ->setFerretFieldKey(PhabricatorSearchDocumentFieldType::FIELD_ALL),
+ id(new FerretConfigurableSearchFunction())
+ ->setFerretFunctionName('title')
+ ->setFerretFieldKey(PhabricatorSearchDocumentFieldType::FIELD_TITLE),
+ id(new FerretConfigurableSearchFunction())
+ ->setFerretFunctionName('body')
+ ->setFerretFieldKey(PhabricatorSearchDocumentFieldType::FIELD_BODY),
+ id(new FerretConfigurableSearchFunction())
+ ->setFerretFunctionName('core')
+ ->setFerretFieldKey(PhabricatorSearchDocumentFieldType::FIELD_CORE),
+ id(new FerretConfigurableSearchFunction())
+ ->setFerretFunctionName('comment')
+ ->setFerretFieldKey(PhabricatorSearchDocumentFieldType::FIELD_COMMENT),
+ );
+ }
+
}
diff --git a/src/applications/search/ferret/PhabricatorFerretEngine.php b/src/applications/search/ferret/PhabricatorFerretEngine.php
--- a/src/applications/search/ferret/PhabricatorFerretEngine.php
+++ b/src/applications/search/ferret/PhabricatorFerretEngine.php
@@ -2,6 +2,10 @@
abstract class PhabricatorFerretEngine extends Phobject {
+ private $fieldMap = array();
+ private $ferretFunctions;
+ private $templateObject;
+
abstract public function getApplicationName();
abstract public function getScopeName();
abstract public function newSearchEngine();
@@ -14,39 +18,31 @@
return 1000;
}
- public function getFieldForFunction($function) {
- $function = phutil_utf8_strtolower($function);
+ final public function getFunctionForName($raw_name) {
+ if (isset($this->fieldMap[$raw_name])) {
+ return $this->fieldMap[$raw_name];
+ }
+
+ $normalized_name =
+ FerretSearchFunction::getNormalizedFunctionName($raw_name);
- $map = $this->getFunctionMap();
- if (!isset($map[$function])) {
+ if ($this->ferretFunctions === null) {
+ $functions = FerretSearchFunction::newFerretSearchFunctions();
+ $this->ferretFunctions = $functions;
+ }
+
+ if (!isset($this->ferretFunctions[$normalized_name])) {
throw new PhutilSearchQueryCompilerSyntaxException(
pht(
'Unknown search function "%s". Supported functions are: %s.',
- $function,
- implode(', ', array_keys($map))));
+ $raw_name,
+ implode(', ', array_keys($this->ferretFunctions))));
}
- return $map[$function]['field'];
- }
+ $function = $this->ferretFunctions[$normalized_name];
+ $this->fieldMap[$raw_name] = $function;
- private function getFunctionMap() {
- return array(
- 'all' => array(
- 'field' => PhabricatorSearchDocumentFieldType::FIELD_ALL,
- ),
- 'title' => array(
- 'field' => PhabricatorSearchDocumentFieldType::FIELD_TITLE,
- ),
- 'body' => array(
- 'field' => PhabricatorSearchDocumentFieldType::FIELD_BODY,
- ),
- 'core' => array(
- 'field' => PhabricatorSearchDocumentFieldType::FIELD_CORE,
- ),
- 'comment' => array(
- 'field' => PhabricatorSearchDocumentFieldType::FIELD_COMMENT,
- ),
- );
+ return $this->fieldMap[$raw_name];
}
public function newStemmer() {
diff --git a/src/applications/search/ferret/function/FerretConfigurableSearchFunction.php b/src/applications/search/ferret/function/FerretConfigurableSearchFunction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/search/ferret/function/FerretConfigurableSearchFunction.php
@@ -0,0 +1,31 @@
+<?php
+
+final class FerretConfigurableSearchFunction
+ extends FerretSearchFunction {
+
+ private $ferretFunctionName;
+ private $ferretFieldKey;
+
+ public function supportsObject(PhabricatorFerretInterface $object) {
+ return true;
+ }
+
+ public function setFerretFunctionName($ferret_function_name) {
+ $this->ferretFunctionName = $ferret_function_name;
+ return $this;
+ }
+
+ public function getFerretFunctionName() {
+ return $this->ferretFunctionName;
+ }
+
+ public function setFerretFieldKey($ferret_field_key) {
+ $this->ferretFieldKey = $ferret_field_key;
+ return $this;
+ }
+
+ public function getFerretFieldKey() {
+ return $this->ferretFieldKey;
+ }
+
+}
diff --git a/src/applications/search/ferret/function/FerretSearchFunction.php b/src/applications/search/ferret/function/FerretSearchFunction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/search/ferret/function/FerretSearchFunction.php
@@ -0,0 +1,122 @@
+<?php
+
+abstract class FerretSearchFunction
+ extends Phobject {
+
+ abstract public function getFerretFunctionName();
+ abstract public function getFerretFieldKey();
+ abstract public function supportsObject(PhabricatorFerretInterface $object);
+
+ final public static function getNormalizedFunctionName($name) {
+ return phutil_utf8_strtolower($name);
+ }
+
+ final public static function validateFerretFunctionName($function_name) {
+ if (!preg_match('/^[a-zA-Z-]+\z/', $function_name)) {
+ throw new Exception(
+ pht(
+ 'Ferret search engine function name ("%s") is invalid. Function '.
+ 'names must be nonempty and may only contain latin letters and '.
+ 'hyphens.'));
+ }
+ }
+
+ final public static function validateFerretFunctionFieldKey($field_key) {
+ if (!preg_match('/^[a-z]{4}\z/', $field_key)) {
+ throw new Exception(
+ pht(
+ 'Ferret search engine field key ("%s") is invalid. Field keys '.
+ 'must be exactly four characters long and contain only '.
+ 'lowercase latin letters.',
+ $field_key));
+ }
+ }
+
+ final public static function newFerretSearchFunctions() {
+ $extensions = PhabricatorFulltextEngineExtension::getAllExtensions();
+
+ $function_map = array();
+ $field_map = array();
+ $results = array();
+
+ foreach ($extensions as $extension) {
+ $functions = $extension->newFerretSearchFunctions();
+
+ if (!is_array($functions)) {
+ throw new Exception(
+ pht(
+ 'Expected fulltext engine extension ("%s") to return a '.
+ 'list of functions from "newFerretSearchFunctions()", '.
+ 'got "%s".',
+ get_class($extension),
+ phutil_describe_type($functions)));
+ }
+
+ foreach ($functions as $idx => $function) {
+ if (!($function instanceof FerretSearchFunction)) {
+ throw new Exception(
+ pht(
+ 'Expected fulltext engine extension ("%s") to return a list '.
+ 'of "FerretSearchFunction" objects from '.
+ '"newFerretSearchFunctions()", but found something else '.
+ '("%s") at index "%s".',
+ get_class($extension),
+ phutil_describe_type($function),
+ $idx));
+ }
+
+ $function_name = $function->getFerretFunctionName();
+
+ self::validateFerretFunctionName($function_name);
+
+ $normal_name = self::getNormalizedFunctionName(
+ $function_name);
+ if ($normal_name !== $function_name) {
+ throw new Exception(
+ pht(
+ 'Ferret function "%s" is specified with a denormalized name. '.
+ 'Instead, specify the function using the normalized '.
+ 'function name ("%s").',
+ $normal_name));
+ }
+
+ if (isset($function_map[$function_name])) {
+ $other_extension = $function_map[$function_name];
+ throw new Exception(
+ pht(
+ 'Two different fulltext engine extensions ("%s" and "%s") '.
+ 'both define a search function with the same name ("%s"). '.
+ 'Each function must have a unique name.',
+ get_class($extension),
+ get_class($other_extension),
+ $function_name));
+ }
+ $function_map[$function_name] = $extension;
+
+ $field_key = $function->getFerretFieldKey();
+
+ self::validateFerretFunctionFieldKey($field_key);
+
+ if (isset($field_map[$field_key])) {
+ $other_extension = $field_map[$field_key];
+ throw new Exception(
+ pht(
+ 'Two different fulltext engine extensions ("%s" and "%s") '.
+ 'both define a search function with the same key ("%s"). '.
+ 'Each function must have a unique key.',
+ get_class($extension),
+ get_class($other_extension),
+ $field_key));
+ }
+ $field_map[$field_key] = $extension;
+
+ $results[$function_name] = $function;
+ }
+ }
+
+ ksort($results);
+
+ return $results;
+ }
+
+}
diff --git a/src/applications/search/index/PhabricatorFulltextEngineExtension.php b/src/applications/search/index/PhabricatorFulltextEngineExtension.php
--- a/src/applications/search/index/PhabricatorFulltextEngineExtension.php
+++ b/src/applications/search/index/PhabricatorFulltextEngineExtension.php
@@ -39,4 +39,8 @@
->execute();
}
+ public function newFerretSearchFunctions() {
+ return array();
+ }
+
}
diff --git a/src/applications/search/query/PhabricatorFulltextToken.php b/src/applications/search/query/PhabricatorFulltextToken.php
--- a/src/applications/search/query/PhabricatorFulltextToken.php
+++ b/src/applications/search/query/PhabricatorFulltextToken.php
@@ -43,6 +43,12 @@
$tip = null;
$icon = null;
+ $name = $token->getValue();
+ $function = $token->getFunction();
+ if ($function !== null) {
+ $name = pht('%s: %s', $function, $name);
+ }
+
if ($this->getIsShort()) {
$shade = PHUITagView::COLOR_GREY;
$tip = pht('Ignored Short Word');
@@ -64,6 +70,14 @@
$tip = pht('Exact Search');
$shade = PHUITagView::COLOR_GREEN;
break;
+ case PhutilSearchQueryCompiler::OPERATOR_PRESENT:
+ $name = pht('Field Present: %s', $function);
+ $shade = PHUITagView::COLOR_GREEN;
+ break;
+ case PhutilSearchQueryCompiler::OPERATOR_ABSENT:
+ $name = pht('Field Absent: %s', $function);
+ $shade = PHUITagView::COLOR_RED;
+ break;
default:
$shade = PHUITagView::COLOR_BLUE;
break;
@@ -73,7 +87,7 @@
$tag = id(new PHUITagView())
->setType(PHUITagView::TYPE_SHADE)
->setColor($shade)
- ->setName($token->getValue());
+ ->setName($name);
if ($tip !== null) {
Javelin::initBehavior('phabricator-tooltips');
diff --git a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php
--- a/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php
+++ b/src/infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php
@@ -1815,7 +1815,7 @@
$function = $default_function;
}
- $raw_field = $engine->getFieldForFunction($function);
+ $function_def = $engine->getFunctionForName($function);
// NOTE: The query compiler guarantees that a query can not make a
// field both "present" and "absent", so it's safe to just use the
@@ -1829,7 +1829,7 @@
$alias = 'ftfield_'.$idx++;
$table_map[$function] = array(
'alias' => $alias,
- 'key' => $raw_field,
+ 'function' => $function_def,
'optional' => $is_optional,
);
}
@@ -1838,7 +1838,7 @@
// Join the title field separately so we can rank results.
$table_map['rank'] = array(
'alias' => 'ft_rank',
- 'key' => PhabricatorSearchDocumentFieldType::FIELD_TITLE,
+ 'function' => $engine->getFunctionForName('title'),
// See T13345. Not every document has a title, so we want to LEFT JOIN
// this table to avoid excluding documents with no title that match
@@ -2130,6 +2130,36 @@
$ngram);
}
+ $object = $this->newResultObject();
+ if (!$object) {
+ throw new Exception(
+ pht(
+ 'Query class ("%s") must define "newResultObject()" to use '.
+ 'Ferret constraints.',
+ get_class($this)));
+ }
+
+ // See T13511. If we have a fulltext query which uses valid field
+ // functions, but at least one of the functions applies to a field which
+ // the object can never have, the query can never match anything. Detect
+ // this and return an empty result set.
+
+ // (Even if the query is "field is absent" or "field does not contain
+ // such-and-such", the interpretation is that these constraints are
+ // not meaningful when applied to an object which can never have the
+ // field.)
+
+ $functions = ipull($this->ferretTables, 'function');
+ $functions = mpull($functions, null, 'getFerretFunctionName');
+ foreach ($functions as $function) {
+ if (!$function->supportsObject($object)) {
+ throw new PhabricatorEmptyQueryException(
+ pht(
+ 'This query uses a fulltext function which this document '.
+ 'type does not support.'));
+ }
+ }
+
foreach ($this->ferretTables as $table) {
$alias = $table['alias'];
@@ -2148,7 +2178,7 @@
$alias,
$alias,
$alias,
- $table['key']);
+ $table['function']->getFerretFieldKey());
}
return $joins;
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Tue, Nov 26, 2:58 AM (21 h, 18 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6787847
Default Alt Text
D21131.diff (16 KB)
Attached To
Mode
D21131: Modularize Ferret fulltext functions
Attached
Detach File
Event Timeline
Log In to Comment