Page MenuHomePhabricator

D21109.id.diff
No OneTemporary

D21109.id.diff

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
@@ -172,8 +172,10 @@
}
if ($mode == 'operator') {
- if (preg_match('/^\s\z/u', $character)) {
- continue;
+ if (!$current_operator) {
+ if (preg_match('/^\s\z/u', $character)) {
+ continue;
+ }
}
if (preg_match('/^'.$operator_characters.'\z/', $character)) {
@@ -337,6 +339,7 @@
'operator' => $operator,
'quoted' => $is_quoted,
'value' => $value,
+ 'raw' => $this->getDisplayToken($token),
);
if ($enable_functions) {
@@ -355,16 +358,58 @@
$result['function'] = $function;
- if ($result['quoted']) {
- $last_function = null;
- } else {
+ $is_sticky = !$result['quoted'];
+ switch ($operator) {
+ case self::OPERATOR_ABSENT:
+ case self::OPERATOR_PRESENT:
+ $is_sticky = false;
+ break;
+ }
+
+ if ($is_sticky) {
$last_function = $function;
+ } else {
+ $last_function = null;
}
}
$results[] = $result;
}
+ if ($enable_functions) {
+ // If any function is required to be "absent", there must be no other
+ // terms which make assertions about it.
+
+ $present_tokens = array();
+ $absent_tokens = array();
+ foreach ($results as $result) {
+ $function = $result['function'];
+
+ if ($result['operator'] === self::OPERATOR_ABSENT) {
+ $absent_tokens[$function][] = $result;
+ } else {
+ $present_tokens[$function][] = $result;
+ }
+ }
+
+ foreach ($absent_tokens as $function => $tokens) {
+ $absent_token = head($tokens);
+
+ if (empty($present_tokens[$function])) {
+ continue;
+ }
+
+ $present_token = head($present_tokens[$function]);
+
+ throw new PhutilSearchQueryCompilerSyntaxException(
+ pht(
+ 'Query field must be absent ("%s") and present ("%s"). This '.
+ 'is impossible, so the query is not valid.',
+ $absent_token['raw'],
+ $present_token['raw']));
+ }
+ }
+
return $results;
}
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
@@ -10,10 +10,6 @@
'cat -dog' => '+"cat" -"dog"',
'cat-dog' => '+"cat-dog"',
- // If there are spaces after an operator, the operator applies to the
- // next search term.
- 'cat - dog' => '+"cat" -"dog"',
-
// Double quotes serve as delimiters even if there is no whitespace
// between terms.
'"cat"dog' => '+"cat" +"dog"',
@@ -40,9 +36,16 @@
// Trailing whitespace should be discarded.
'a b ' => '+"a" +"b"',
- // Functions must have search text.
+ // Tokens must have search text.
'""' => false,
'-' => false,
+
+ // Previously, we permitted spaces to appear inside or after operators.
+
+ // Now that "title:-" is now a valid construction meaning "title is
+ // absent", this had to be tightened. We want "title:- duck" to mean
+ // "title is absent, and any other field matches 'duck'".
+ 'cat - dog' => false,
);
$this->assertCompileQueries($tests);
@@ -171,6 +174,21 @@
array('title', $op_and, 'x'),
array(null, $op_and, 'y'),
),
+
+ // The "present" and "absent" functions are not sticky.
+ 'title:~ x' => array(
+ array('title', $op_present, null),
+ array(null, $op_and, 'x'),
+ ),
+ 'title:- x' => array(
+ array('title', $op_absent, null),
+ array(null, $op_and, 'x'),
+ ),
+
+ // These queries require a field be both present and absent, which is
+ // impossible.
+ 'title:- title:x' => false,
+ 'title:- title:~' => false,
);
$this->assertCompileFunctionQueries($function_tests);

File Metadata

Mime Type
text/plain
Expires
Sat, May 11, 12:58 PM (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6284738
Default Alt Text
D21109.id.diff (4 KB)

Event Timeline