3 final class PhutilSearchQueryCompilerTestCase
4 extends PhutilTestCase
{
6 public function testCompileQueries() {
9 'cat dog' => '+"cat" +"dog"',
10 'cat -dog' => '+"cat" -"dog"',
11 'cat-dog' => '+"cat-dog"',
13 // Double quotes serve as delimiters even if there is no whitespace
15 '"cat"dog' => '+"cat" +"dog"',
17 // This query is too long.
18 str_repeat('x', 2048) => false,
20 // Multiple operators are not permitted.
25 // Stray operators are not permitted.
29 // Double quotes must be paired.
34 'A"B"' => '+"A" +"B"',
36 // Trailing whitespace should be discarded.
37 'a b ' => '+"a" +"b"',
39 // Tokens must have search text.
43 // Previously, we permitted spaces to appear inside or after operators.
45 // Now that "title:-" is now a valid construction meaning "title is
46 // absent", this had to be tightened. We want "title:- duck" to mean
47 // "title is absent, and any other field matches 'duck'".
51 $this->assertCompileQueries($tests);
53 // Test that we compile queries correctly if the operators have been
54 // swapped to use "AND" by default.
55 $operator_tests = array(
56 'cat dog' => '"cat" "dog"',
57 'cat -dog' => '"cat" -"dog"',
59 $this->assertCompileQueries($operator_tests, ' |-><()~*:""&\'');
62 // Test that we compile queries correctly if the quote operators have
63 // been swapped to differ.
65 'cat dog' => '+[cat] +[dog]',
66 'cat -dog' => '+[cat] -[dog]',
68 $this->assertCompileQueries($quote_tests, '+ -><()~*:[]&|');
71 public function testCompileQueriesWithStemming() {
72 $stemming_tests = array(
81 'cats "dogs"' => array(
85 '"blessed blade" of the windseeker' => array(
87 '+"of" +"the" +"windseek"',
89 'mailing users for mentions on tasks' => array(
91 '+"mail" +"user" +"for" +"mention" +"on" +"task"',
95 $stemmer = new PhutilSearchStemmer();
96 $this->assertCompileQueries($stemming_tests, null, $stemmer);
99 public function testCompileQueriesWithFunctions() {
100 $op_and = PhutilSearchQueryCompiler
::OPERATOR_AND
;
101 $op_sub = PhutilSearchQueryCompiler
::OPERATOR_SUBSTRING
;
102 $op_exact = PhutilSearchQueryCompiler
::OPERATOR_EXACT
;
103 $op_present = PhutilSearchQueryCompiler
::OPERATOR_PRESENT
;
104 $op_absent = PhutilSearchQueryCompiler
::OPERATOR_ABSENT
;
106 $mao = "\xE7\x8C\xAB";
108 $function_tests = array(
110 array(null, $op_and, 'cat'),
113 array(null, $op_and, 'cat'),
115 'title:cat' => array(
116 array('title', $op_and, 'cat'),
118 'title:cat:dog' => array(
119 array('title', $op_and, 'cat:dog'),
121 'title:~cat' => array(
122 array('title', $op_sub, 'cat'),
124 'cat title:="Meow Meow"' => array(
125 array(null, $op_and, 'cat'),
126 array('title', $op_exact, 'Meow Meow'),
128 'title:cat title:dog' => array(
129 array('title', $op_and, 'cat'),
130 array('title', $op_and, 'dog'),
132 '~"core and seven years ag"' => array(
133 array(null, $op_sub, 'core and seven years ag'),
136 array(null, $op_sub, $mao),
139 array(null, $op_and, $mao),
142 array(null, $op_sub, $mao),
144 '"'.$mao.'"' => array(
145 array(null, $op_and, $mao),
149 'title:+""' => false,
153 array('title', $op_present, null),
157 array('title', $op_absent, null),
163 // Functions like "title:" apply to following terms if their term is
164 // not specified with double quotes.
165 'title:x y' => array(
166 array('title', $op_and, 'x'),
167 array('title', $op_and, 'y'),
169 'title: x y' => array(
170 array('title', $op_and, 'x'),
171 array('title', $op_and, 'y'),
173 'title:"x" y' => array(
174 array('title', $op_and, 'x'),
175 array(null, $op_and, 'y'),
178 // The "present" and "absent" functions are not sticky.
179 'title:~ x' => array(
180 array('title', $op_present, null),
181 array(null, $op_and, 'x'),
183 'title:- x' => array(
184 array('title', $op_absent, null),
185 array(null, $op_and, 'x'),
188 // Functions like "title:" continue to stick across quotes if the
189 // quotes aren't the initial argument.
190 'title:a "b c" d' => array(
191 array('title', $op_and, 'a'),
192 array('title', $op_and, 'b c'),
193 array('title', $op_and, 'd'),
196 // These queries require a field be both present and absent, which is
198 'title:- title:x' => false,
199 'title:- title:~' => false,
201 'abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ:xyz' => array(
203 'abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ',
209 // See T12995. Interpret CJK tokens as substring queries since these
210 // languages do not use spaces as word separators.
211 "\xE7\x8C\xAB" => array(
212 array(null, $op_sub, "\xE7\x8C\xAB"),
215 // See T13632. Interpret tokens that begin with "_" as substring tokens
216 // if no function is specified.
217 '_x _y_ "_z_"' => array(
218 array(null, $op_sub, '_x'),
219 array(null, $op_sub, '_y_'),
220 array(null, $op_and, '_z_'),
224 $this->assertCompileFunctionQueries($function_tests);
227 private function assertCompileQueries(
230 PhutilSearchStemmer
$stemmer = null) {
231 foreach ($tests as $input => $expect) {
235 $literal_query = null;
236 $stemmed_query = null;
239 $compiler = new PhutilSearchQueryCompiler();
241 if ($operators !== null) {
242 $compiler->setOperators($operators);
245 if ($stemmer !== null) {
246 $compiler->setStemmer($stemmer);
249 $tokens = $compiler->newTokens($input);
252 $literal_query = $compiler->compileLiteralQuery($tokens);
253 $stemmed_query = $compiler->compileStemmedQuery($tokens);
255 $query = $compiler->compileQuery($tokens);
257 } catch (PhutilSearchQueryCompilerSyntaxException
$ex) {
261 if ($caught !== null) {
263 $literal_query = false;
264 $stemmed_query = false;
271 pht('Compilation of query: %s', $input));
275 ($literal_query === false)
277 : array($literal_query, $stemmed_query),
278 pht('Stemmed compilation of query: %s', $input));
283 private function assertCompileFunctionQueries(array $tests) {
284 foreach ($tests as $input => $expect) {
285 $compiler = id(new PhutilSearchQueryCompiler())
286 ->setEnableFunctions(true);
289 $tokens = $compiler->newTokens($input);
292 foreach ($tokens as $token) {
294 $token->getFunction(),
295 $token->getOperator(),
299 } catch (PhutilSearchQueryCompilerSyntaxException
$ex) {
306 pht('Function compilation of query: %s', $input));