Do not treat fullwidth Latin and symbols as unbroken script
[xapian.git] / xapian-core / tests / api_queryparser.cc
bloba2511cfb2a78483256c5faf25df63dd60f6a86ff
1 /** @file
2 * @brief Tests of Xapian::QueryParser
3 */
4 /* Copyright (C) 2002-2022 Olly Betts
5 * Copyright (C) 2006,2007,2009 Lemur Consulting Ltd
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
20 * USA
23 #include <config.h>
25 #include "api_queryparser.h"
27 #define XAPIAN_DEPRECATED(D) D
28 #include <xapian.h>
30 #include "apitest.h"
31 #include "cputimer.h"
32 #include "str.h"
33 #include "stringutils.h"
35 #include <string>
36 #include <vector>
38 using namespace std;
40 #include "testsuite.h"
41 #include "testutils.h"
43 struct test {
44 const char *query;
45 const char *expect;
48 static const test test_or_queries[] = {
49 { "simple-example", "(simple@1 PHRASE 2 example@2)" },
50 { "time_t", "Ztime_t@1" },
51 { "stock -cooking", "(Zstock@1 AND_NOT Zcook@2)" },
52 { "foo -baz bar", "((Zfoo@1 OR Zbar@3) AND_NOT Zbaz@2)" },
53 { "d- school report", "(Zd@1 OR (Zschool@2 OR Zreport@3))" },
54 { "gtk+ -gnome", "(Zgtk+@1 AND_NOT Zgnome@2)" },
55 { "c++ -d--", "(Zc++@1 AND_NOT Zd@2)" },
56 { "Mg2+ Cl-", "(mg2+@1 OR cl@2)" },
57 { "\"c++ library\"", "(c++@1 PHRASE 2 library@2)" },
58 { "A&L A&RMCO AD&D", "(a&l@1 OR a&rmco@2 OR ad&d@3)" },
59 { "C# vs C++", "(c#@1 OR Zvs@2 OR c++@3)" },
60 { "j##", "Zj##@1" },
61 { "a#b", "(Za@1 OR Zb@2)" },
62 { "O.K. U.N.C.L.E XY.Z.", "(ok@1 OR uncle@2 OR (xy@3 PHRASE 2 z@4))" },
63 { "author:orwell animal farm", "(ZAorwel@1 OR Zanim@2 OR Zfarm@3)" },
64 { "author:Orwell Animal Farm", "(Aorwell@1 OR animal@2 OR farm@3)" },
65 // Regression test for bug reported in 0.9.6.
66 { "author:\"orwell\" title:\"animal\"", "(Aorwell@1 OR XTanimal@2)" },
67 // Regression test for bug related to one reported in 0.9.6.
68 { "author:(orwell) title:(animal)", "(ZAorwel@1 OR ZXTanim@2)" },
69 // Regression test for bug caused by fix for previous bug.
70 { "author:\"milne, a.a.\"", "(Amilne@1 PHRASE 3 Aa@2 PHRASE 3 Aa@3)" },
71 { "author:\"milne a.a.\"", "(Amilne@1 PHRASE 3 Aa@2 PHRASE 3 Aa@3)" },
72 // Regression test for bug reported in 0.9.7.
73 { "site:/path/name", "0 * H/path/name" },
74 // Regression test for bug introduced (and fixed) in SVN prior to 1.0.0.
75 { "author:/path/name", "(Apath@1 PHRASE 2 Aname@2)" },
76 // Feature tests for change to allow phrase generators after prefix in 1.2.4.
77 { "author:/path", "ZApath@1" },
78 { "author:-Foo", "Afoo@1" },
79 { "author:/", "Zauthor@1" },
80 { "author::", "Zauthor@1" },
81 { "author:/ foo", "(Zauthor@1 OR Zfoo@2)" },
82 { "author:: foo", "(Zauthor@1 OR Zfoo@2)" },
83 { "author::foo", "(author@1 PHRASE 2 foo@2)" },
84 { "author:/ AND foo", "(Zauthor@1 AND Zfoo@2)" },
85 { "author:: AND foo", "(Zauthor@1 AND Zfoo@2)" },
86 { "foo AND author:/", "(Zfoo@1 AND Zauthor@2)" },
87 { "foo AND author::", "(Zfoo@1 AND Zauthor@2)" },
88 // Regression test for bug introduced into (and fixed) in SVN prior to 1.0.0.
89 { "author:(title::case)", "(Atitle@1 PHRASE 2 Acase@2)" },
90 // Regression test for bug fixed in 1.0.4 - the '+' would be ignored there
91 // because the whitespace after the '"' wasn't noticed.
92 { "\"hello world\" +python", "(Zpython@3 AND_MAYBE (hello@1 PHRASE 2 world@2))" },
93 // In 1.1.0, NON_SPACING_MARK was added as a word character.
94 { "\xd8\xa7\xd9\x84\xd8\xb1\xd9\x91\xd8\xad\xd9\x85\xd9\x86", "Z\xd8\xa7\xd9\x84\xd8\xb1\xd9\x91\xd8\xad\xd9\x85\xd9\x86@1" },
95 // In 1.1.4, ENCLOSING_MARK and COMBINING_SPACING_MARK were added, and
96 // code to ignore several zero-width space characters was added.
97 { "\xe1\x80\x9d\xe1\x80\xae\xe2\x80\x8b\xe1\x80\x80\xe1\x80\xae\xe2\x80\x8b\xe1\x80\x95\xe1\x80\xad\xe2\x80\x8b\xe1\x80\x9e\xe1\x80\xaf\xe1\x80\xb6\xe1\x80\xb8\xe2\x80\x8b\xe1\x80\x85\xe1\x80\xbd\xe1\x80\xb2\xe2\x80\x8b\xe1\x80\x9e\xe1\x80\xb0\xe2\x80\x8b\xe1\x80\x99\xe1\x80\xbb\xe1\x80\xac\xe1\x80\xb8\xe1\x80\x80", "Z\xe1\x80\x9d\xe1\x80\xae\xe1\x80\x80\xe1\x80\xae\xe1\x80\x95\xe1\x80\xad\xe1\x80\x9e\xe1\x80\xaf\xe1\x80\xb6\xe1\x80\xb8\xe1\x80\x85\xe1\x80\xbd\xe1\x80\xb2\xe1\x80\x9e\xe1\x80\xb0\xe1\x80\x99\xe1\x80\xbb\xe1\x80\xac\xe1\x80\xb8\xe1\x80\x80@1" },
98 { "unmatched\"", "unmatched@1" },
99 { "unmatched \" \" ", "Zunmatch@1" },
100 { "hyphen-ated\" ", "(hyphen@1 PHRASE 2 ated@2)" },
101 { "hyphen-ated\" \"", "(hyphen@1 PHRASE 2 ated@2)" },
102 { "\"1.4\"", "1.4@1" },
103 { "\"1.\"", "1@1" },
104 { "\"A#.B.\"", "(a#@1 PHRASE 2 b@2)" },
105 { "\" Xapian QueryParser\" parses queries", "((xapian@1 PHRASE 2 queryparser@2) OR (Zpars@3 OR Zqueri@4))" },
106 { "\" xapian queryParser\" parses queries", "((xapian@1 PHRASE 2 queryparser@2) OR (Zpars@3 OR Zqueri@4))" },
107 { "h\xc3\xb6hle", "Zh\xc3\xb6hle@1" },
108 { "one +two three", "(Ztwo@2 AND_MAYBE (Zone@1 OR Zthree@3))" },
109 { "subject:test other", "(ZXTtest@1 OR Zother@2)" },
110 { "subject:\"space flight\"", "(XTspace@1 PHRASE 2 XTflight@2)" },
111 { "author:(twain OR poe) OR flight", "(ZAtwain@1 OR ZApoe@2 OR Zflight@3)" },
112 { "author:(twain OR title:pit OR poe)", "(ZAtwain@1 OR ZXTpit@2 OR ZApoe@3)" },
113 { "title:2001 title:space", "(XT2001@1 OR ZXTspace@2)" },
114 { "(title:help)", "ZXThelp@1" },
115 { "beer NOT \"orange juice\"", "(Zbeer@1 AND_NOT (orange@2 PHRASE 2 juice@3))" },
116 { "beer AND NOT lager", "(Zbeer@1 AND_NOT Zlager@2)" },
117 { "beer AND -lager", "(Zbeer@1 AND_NOT Zlager@2)" },
118 { "beer AND +lager", "(Zbeer@1 AND Zlager@2)" },
119 { "A OR B NOT C", "(a@1 OR (b@2 AND_NOT c@3))" },
120 { "A OR B AND NOT C", "(a@1 OR (b@2 AND_NOT c@3))" },
121 { "A OR B AND -C", "(a@1 OR (b@2 AND_NOT c@3))" },
122 { "A OR B AND +C", "(a@1 OR (b@2 AND c@3))" },
123 { "A OR B XOR C", "(a@1 OR (b@2 XOR c@3))" },
124 { "A XOR B NOT C", "(a@1 XOR (b@2 AND_NOT c@3))" },
125 { "one AND two", "(Zone@1 AND Ztwo@2)" },
126 { "one A.N.D. two", "(Zone@1 OR and@2 OR Ztwo@3)" },
127 { "one \xc3\x81ND two", "(Zone@1 OR \xc3\xa1nd@2 OR Ztwo@3)" },
128 { "one author:AND two", "(Zone@1 OR Aand@2 OR Ztwo@3)" },
129 { "author:hyphen-ated", "(Ahyphen@1 PHRASE 2 Aated@2)" },
130 { "cvs site:xapian.org", "(Zcvs@1 FILTER Hxapian.org)" },
131 { "cvs -site:xapian.org", "(Zcvs@1 AND_NOT Hxapian.org)" },
132 { "foo -site:xapian.org bar", "((Zfoo@1 OR Zbar@2) AND_NOT Hxapian.org)" },
133 { "site:xapian.org mail", "(Zmail@1 FILTER Hxapian.org)" },
134 { "-site:xapian.org mail", "(Zmail@1 AND_NOT Hxapian.org)" },
135 { "mail AND -site:xapian.org", "(Zmail@1 AND_NOT Hxapian.org)" },
136 { "-Wredundant-decls", "(wredundant@1 PHRASE 2 decls@2)" },
137 { "site:xapian.org", "0 * Hxapian.org" },
138 { "mug +site:xapian.org -site:cvs.xapian.org", "((Zmug@1 FILTER Hxapian.org) AND_NOT Hcvs.xapian.org)" },
139 { "mug -site:cvs.xapian.org +site:xapian.org", "((Zmug@1 FILTER Hxapian.org) AND_NOT Hcvs.xapian.org)" },
140 { "mug +site:xapian.org AND -site:cvs.xapian.org", "((Zmug@1 FILTER Hxapian.org) AND_NOT Hcvs.xapian.org)" },
141 { "mug site:xapian.org AND -site:cvs.xapian.org", "((Zmug@1 FILTER Hxapian.org) AND_NOT Hcvs.xapian.org)" },
142 { "mug site:xapian.org AND +site:cvs.xapian.org", "((Zmug@1 FILTER Hxapian.org) AND 0 * Hcvs.xapian.org)" },
143 { "NOT windows", "Syntax: <expression> NOT <expression>" },
144 { "a AND (NOT b)", "Syntax: <expression> NOT <expression>" },
145 { "AND NOT windows", "Syntax: <expression> AND NOT <expression>" },
146 { "AND -windows", "Syntax: <expression> AND <expression>" },
147 { "gordian NOT", "Syntax: <expression> NOT <expression>" },
148 { "gordian AND NOT", "Syntax: <expression> AND NOT <expression>" },
149 { "gordian AND -", "Syntax: <expression> AND <expression>" },
150 { "foo OR (something AND)", "Syntax: <expression> AND <expression>" },
151 { "OR foo", "Syntax: <expression> OR <expression>" },
152 { "XOR", "Syntax: <expression> XOR <expression>" },
153 // Regression test for bug fix in 1.4.13.
154 { "a OR -b", "Syntax: <expression> OR <expression>" },
155 { "hard\xa0space", "(Zhard@1 OR Zspace@2)" },
156 { " white\r\nspace\ttest ", "(Zwhite@1 OR Zspace@2 OR Ztest@3)" },
157 { "one AND two three", "(Zone@1 AND (Ztwo@2 OR Zthree@3))" },
158 { "one two AND three", "((Zone@1 OR Ztwo@2) AND Zthree@3)" },
159 { "one AND two/three", "(Zone@1 AND (two@2 PHRASE 2 three@3))" },
160 { "one AND /two/three", "(Zone@1 AND (two@2 PHRASE 2 three@3))" },
161 { "one AND/two/three", "(Zone@1 AND (two@2 PHRASE 2 three@3))" },
162 { "one +/two/three", "((two@2 PHRASE 2 three@3) AND_MAYBE Zone@1)" },
163 { "one//two", "(one@1 PHRASE 2 two@2)" },
164 { "\"missing quote", "(missing@1 PHRASE 2 quote@2)" },
165 { "DVD+RW", "(dvd@1 OR rw@2)" }, // Would a phrase be better?
166 { "+\"must have\" optional", "((must@1 PHRASE 2 have@2) AND_MAYBE Zoption@3)" },
167 { "one NEAR two NEAR three", "(one@1 NEAR 12 two@2 NEAR 12 three@3)" },
168 { "something NEAR/3 else", "(something@1 NEAR 4 else@2)" },
169 { "a NEAR/6 b NEAR c", "(a@1 NEAR 8 b@2 NEAR 8 c@3)" },
170 { "something ADJ else", "(something@1 PHRASE 11 else@2)" },
171 { "something ADJ/3 else", "(something@1 PHRASE 4 else@2)" },
172 { "a ADJ/6 b ADJ c", "(a@1 PHRASE 8 b@2 PHRASE 8 c@3)" },
173 { "dog SYN mutt SYN cur SYN pug", "(((Zdog@1 SYNONYM Zmutt@2) SYNONYM Zcur@3) SYNONYM Zpug@4)" },
174 { "dog SYN mutt AND food", "((Zdog@1 SYNONYM Zmutt@2) AND Zfood@3)" },
175 { "dog SYN mutt NOT cat", "((Zdog@1 SYNONYM Zmutt@2) AND_NOT Zcat@3)" },
176 // FIXME: These next two don't parse as we want due to how `-` is handled.
177 //{ "dog SYN mutt -cat", "((Zdog@1 SYNONYM Zmutt@2) AND_NOT Zcat@3)" },
178 //{ "dog SYN mutt -\"fire dog\"", "((Zdog@1 SYNONYM Zmutt@2) AND_NOT (fire@3 PHRASE 2 dog@4))" },
179 // Regression test - Unicode character values were truncated to 8 bits
180 // before testing C_isdigit(), so this rather artificial example parsed
181 // to: (a@1 NEAR 262 b@2)
182 { "a NEAR/\xc4\xb5 b", "(Za@1 OR (near@2 PHRASE 2 \xc4\xb5@3) OR Zb@4)" },
183 { "a ADJ/\xc4\xb5 b", "(Za@1 OR (adj@2 PHRASE 2 \xc4\xb5@3) OR Zb@4)" },
184 // Regression test - the first two cases were parsed as if the '/' were a
185 // space, which was inconsistent with the second two. Fixed in 1.2.5.
186 { "a NEAR/b", "(Za@1 OR (near@2 PHRASE 2 b@3))" },
187 { "a ADJ/b", "(Za@1 OR (adj@2 PHRASE 2 b@3))" },
188 { "a NEAR/b c", "(Za@1 OR (near@2 PHRASE 2 b@3) OR Zc@4)" },
189 { "a ADJ/b c", "(Za@1 OR (adj@2 PHRASE 2 b@3) OR Zc@4)" },
190 // Regression tests - + and - didn't work on bracketed subexpressions prior
191 // to 1.0.2.
192 { "+(one two) three", "((Zone@1 OR Ztwo@2) AND_MAYBE Zthree@3)" },
193 { "zero -(one two)", "(Zzero@1 AND_NOT (Zone@2 OR Ztwo@3))" },
194 // Feature tests that ':' is inserted between prefix and term correctly:
195 { "category:Foo", "0 * XCAT:Foo" },
196 { "category:foo", "0 * XCATfoo" },
197 { "category:\xc3\x96oo", "0 * XCAT\xc3\x96oo" },
198 { "category::colon", "0 * XCAT::colon" },
199 // Feature tests for quoted boolean terms:
200 { "category:\"Hello world\"", "0 * XCAT:Hello world" },
201 { "category:\"literal \"\"\"", "0 * XCATliteral \"" },
202 { "category:\" \"", "0 * XCAT " },
203 { "category:\"\"", "0 * XCAT" },
204 { "category:\"(unterminated)", "0 * XCAT(unterminated)" },
205 // Feature tests for curly double quotes:
206 { "“curly quotes”", "(curly@1 PHRASE 2 quotes@2)" },
207 // Test "" inside quoted phrase doesn't end the phrase (for consistency
208 // with "" being an escape " in a quoted boolean term.
209 { "subject:\"foo\"\"bar\"", "(XTfoo@1 PHRASE 2 XTbar@2)" },
210 // Feature tests for implicitly closing brackets:
211 { "(foo", "Zfoo@1" },
212 { "(foo XOR bar", "(Zfoo@1 XOR Zbar@2)" },
213 { "(foo XOR (bar AND baz)", "(Zfoo@1 XOR (Zbar@2 AND Zbaz@3))" },
214 { "(foo XOR (bar AND baz", "(Zfoo@1 XOR (Zbar@2 AND Zbaz@3))" },
215 // Slightly arbitrarily we accept mismatched quotes.
216 { "\"curly quotes”", "(curly@1 PHRASE 2 quotes@2)" },
217 { "“curly quotes\"", "(curly@1 PHRASE 2 quotes@2)" },
218 { "“curly quotes“", "(curly@1 PHRASE 2 quotes@2)" },
219 { "”curly quotes”", "(curly@1 PHRASE 2 quotes@2)" },
220 { "author:“orwell” title:“animal\"", "(Aorwell@1 OR XTanimal@2)" },
221 { "author:\"orwell” title:“animal”", "(Aorwell@1 OR XTanimal@2)" },
222 { "author:“milne, a.a.”", "(Amilne@1 PHRASE 3 Aa@2 PHRASE 3 Aa@3)" },
223 { "author:“milne, a.a.\"", "(Amilne@1 PHRASE 3 Aa@2 PHRASE 3 Aa@3)" },
224 { "author:\"milne a.a.”", "(Amilne@1 PHRASE 3 Aa@2 PHRASE 3 Aa@3)" },
225 { "“hello world” +python", "(Zpython@3 AND_MAYBE (hello@1 PHRASE 2 world@2))" },
226 { "unmatched“", "Zunmatch@1" },
227 { "unmatched”", "Zunmatch@1" },
228 { "unmatched “ ” ", "Zunmatch@1" },
229 { "unmatched \" ” ", "Zunmatch@1" },
230 { "unmatched “ \" ", "Zunmatch@1" },
231 { "hyphen-ated“ ", "(hyphen@1 PHRASE 2 ated@2)" },
232 { "hyphen-ated” ", "(hyphen@1 PHRASE 2 ated@2)" },
233 { "hyphen-ated“ ”", "(hyphen@1 PHRASE 2 ated@2)" },
234 { "hyphen-ated“ \"", "(hyphen@1 PHRASE 2 ated@2)" },
235 { "hyphen-ated\" ”", "(hyphen@1 PHRASE 2 ated@2)" },
236 { "“1.4”", "1.4@1" },
237 { "“1.\"", "1@1" },
238 { "\"A#.B.”", "(a#@1 PHRASE 2 b@2)" },
239 { "“ Xapian QueryParser” parses queries", "((xapian@1 PHRASE 2 queryparser@2) OR (Zpars@3 OR Zqueri@4))" },
240 { "“ xapian queryParser\" parses queries", "((xapian@1 PHRASE 2 queryparser@2) OR (Zpars@3 OR Zqueri@4))" },
241 { "beer NOT “orange juice”", "(Zbeer@1 AND_NOT (orange@2 PHRASE 2 juice@3))" },
242 { "“missing quote", "(missing@1 PHRASE 2 quote@2)" },
243 { "+“must have” optional", "((must@1 PHRASE 2 have@2) AND_MAYBE Zoption@3)" },
244 { "category:“Hello world”", "0 * XCAT:Hello world" },
245 { "category:“literal \"\"”", "0 * XCATliteral \"" },
246 { "category:“ ”", "0 * XCAT " },
247 { "category:\" \"", "0 * XCAT ”" },
248 { "category:\" ”", "0 * XCAT ”" },
249 { "category:“ \"", "0 * XCAT " },
250 { "category:“”", "0 * XCAT" },
251 { "category:\"\"", "0 * XCAT”" },
252 { "category:\"”", "0 * XCAT”" },
253 { "category:“\"", "0 * XCAT" },
254 { "category:“(unterminated)", "0 * XCAT(unterminated)" },
255 // Real world examples from tweakers.net:
256 { "Call to undefined function: imagecreate()", "(call@1 OR Zto@2 OR Zundefin@3 OR Zfunction@4 OR imagecreate@5)" },
257 { "mysql_fetch_row(): supplied argument is not a valid MySQL result resource", "(mysql_fetch_row@1 OR (Zsuppli@2 OR Zargument@3 OR Zis@4 OR Znot@5 OR Za@6 OR Zvalid@7 OR mysql@8 OR Zresult@9 OR Zresourc@10))" },
258 { "php date() nedelands", "(Zphp@1 OR date@2 OR Znedeland@3)" },
259 { "wget domein --http-user", "(Zwget@1 OR Zdomein@2 OR (http@3 PHRASE 2 user@4))" },
260 { "@home problemen", "(Zhome@1 OR Zproblemen@2)" },
261 { "'ipacsum'", "Zipacsum@1" },
262 { "canal + ", "Zcanal@1" },
263 { "/var/run/mysqld/mysqld.sock", "(var@1 PHRASE 5 run@2 PHRASE 5 mysqld@3 PHRASE 5 mysqld@4 PHRASE 5 sock@5)" },
264 { "\"QSI-161 drivers\"", "(qsi@1 PHRASE 3 161@2 PHRASE 3 drivers@3)" },
265 { "\"e-cube\" barebone", "((e@1 PHRASE 2 cube@2) OR Zbarebon@3)" },
266 { "\"./httpd: symbol not found: dlopen\"", "(httpd@1 PHRASE 5 symbol@2 PHRASE 5 not@3 PHRASE 5 found@4 PHRASE 5 dlopen@5)" },
267 { "ERROR 2003: Can't connect to MySQL server on 'localhost' (10061)", "(error@1 OR 2003@2 OR can't@3 OR Zconnect@4 OR Zto@5 OR mysql@6 OR Zserver@7 OR Zon@8 OR Zlocalhost@9 OR 10061@10)" },
268 { "location.href = \"\"", "(location@1 PHRASE 2 href@2)" },
269 { "method=\"post\" action=\"\">", "(method@1 OR post@2 OR action@3)" },
270 { "behuizing 19\" inch", "(Zbehuiz@1 OR 19@2 OR inch@3)" },
271 { "19\" rack", "(19@1 OR rack@2)" },
272 { "3,5\" mainboard", "(3,5@1 OR mainboard@2)" },
273 { "553 sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1)", "(553@1 OR Zsorri@2 OR (Zthat@3 OR Zdomain@4 OR Zisn't@5 OR Zin@6 OR Zmy@7 OR Zlist@8 OR Zof@9 OR Zallow@10 OR Zrcpthost@11) OR 5.7.1@12)" },
274 { "data error (clic redundancy check)", "(Zdata@1 OR Zerror@2 OR (Zclic@3 OR Zredund@4 OR Zcheck@5))" },
275 { "? mediaplayer 9\"", "(Zmediaplay@1 OR 9@2)" },
276 { "date(\"w\")", "(date@1 OR w@2)" },
277 { "Syntaxisfout (operator ontbreekt ASP", "(syntaxisfout@1 OR (Zoper@2 OR Zontbreekt@3 OR asp@4))" },
278 { "Request.ServerVariables(\"logon_user\")", "((request@1 PHRASE 2 servervariables@2) OR logon_user@3)" },
279 { "ASP \"request.form\" van \\\"enctype=\"MULTIPART/FORM-DATA\"\\\"", "(asp@1 OR (request@2 PHRASE 2 form@3) OR Zvan@4 OR enctype@5 OR (multipart@6 PHRASE 3 form@7 PHRASE 3 data@8))" },
280 { "USER ftp (Login failed): Invalid shell: /sbin/nologin", "(user@1 OR Zftp@2 OR (login@3 OR Zfail@4) OR (invalid@5 OR Zshell@6) OR (sbin@7 PHRASE 2 nologin@8))" },
281 { "ip_masq_new(proto=TCP)", "(ip_masq_new@1 OR proto@2 OR tcp@3)" },
282 { "\"document.write(\"", "(document@1 PHRASE 2 write@2)" },
283 { "ERROR 1045: Access denied for user: 'root@localhost' (Using password: NO)", "(error@1 OR 1045@2 OR access@3 OR Zdeni@4 OR Zfor@5 OR Zuser@6 OR (root@7 PHRASE 2 localhost@8) OR (using@9 OR Zpassword@10 OR no@11))" },
284 { "TIP !! subtitles op TV-out (via DVD max g400)", "(tip@1 OR (Zsubtitl@2 OR Zop@3) OR (tv@4 PHRASE 2 out@5) OR (Zvia@6 OR dvd@7 OR Zmax@8 OR Zg400@9))" },
285 { "Gigabyte 8PE667 (de Ultra versie) of Asus A7N8X Deluxe", "(gigabyte@1 OR 8pe667@2 OR (Zde@3 OR ultra@4 OR Zversi@5) OR (Zof@6 OR asus@7 OR a7n8x@8 OR deluxe@9))" },
286 { "\"1) Ze testen 8x AF op de GFFX tegen \"", "(1@1 PHRASE 9 ze@2 PHRASE 9 testen@3 PHRASE 9 8x@4 PHRASE 9 af@5 PHRASE 9 op@6 PHRASE 9 de@7 PHRASE 9 gffx@8 PHRASE 9 tegen@9)" },
287 { "\") Ze houden geen rekening met de kwaliteit van AF. Als ze dat gedaan hadden dan waren ze tot de conclusie gekomen dat Performance AF (dus Bilinear AF) op de 9700Pro goed te vergelijken is met Balanced AF op de GFFX. En dan hadden ze ook gezien dat de GFFX niet kan tippen aan de Quality AF van de 9700Pro.\"", "(ze@1 PHRASE 59 houden@2 PHRASE 59 geen@3 PHRASE 59 rekening@4 PHRASE 59 met@5 PHRASE 59 de@6 PHRASE 59 kwaliteit@7 PHRASE 59 van@8 PHRASE 59 af@9 PHRASE 59 als@10 PHRASE 59 ze@11 PHRASE 59 dat@12 PHRASE 59 gedaan@13 PHRASE 59 hadden@14 PHRASE 59 dan@15 PHRASE 59 waren@16 PHRASE 59 ze@17 PHRASE 59 tot@18 PHRASE 59 de@19 PHRASE 59 conclusie@20 PHRASE 59 gekomen@21 PHRASE 59 dat@22 PHRASE 59 performance@23 PHRASE 59 af@24 PHRASE 59 dus@25 PHRASE 59 bilinear@26 PHRASE 59 af@27 PHRASE 59 op@28 PHRASE 59 de@29 PHRASE 59 9700pro@30 PHRASE 59 goed@31 PHRASE 59 te@32 PHRASE 59 vergelijken@33 PHRASE 59 is@34 PHRASE 59 met@35 PHRASE 59 balanced@36 PHRASE 59 af@37 PHRASE 59 op@38 PHRASE 59 de@39 PHRASE 59 gffx@40 PHRASE 59 en@41 PHRASE 59 dan@42 PHRASE 59 hadden@43 PHRASE 59 ze@44 PHRASE 59 ook@45 PHRASE 59 gezien@46 PHRASE 59 dat@47 PHRASE 59 de@48 PHRASE 59 gffx@49 PHRASE 59 niet@50 PHRASE 59 kan@51 PHRASE 59 tippen@52 PHRASE 59 aan@53 PHRASE 59 de@54 PHRASE 59 quality@55 PHRASE 59 af@56 PHRASE 59 van@57 PHRASE 59 de@58 PHRASE 59 9700pro@59)" },
288 { "\"Ze houden geen rekening met de kwaliteit van AF. Als ze dat gedaan hadden dan waren ze tot de conclusie gekomen dat Performance AF (dus Bilinear AF) op de 9700Pro goed te vergelijken is met Balanced AF op de GFFX. En dan hadden ze ook gezien dat de GFFX niet kan tippen aan de Quality AF van de 9700Pro.\"", "(ze@1 PHRASE 59 houden@2 PHRASE 59 geen@3 PHRASE 59 rekening@4 PHRASE 59 met@5 PHRASE 59 de@6 PHRASE 59 kwaliteit@7 PHRASE 59 van@8 PHRASE 59 af@9 PHRASE 59 als@10 PHRASE 59 ze@11 PHRASE 59 dat@12 PHRASE 59 gedaan@13 PHRASE 59 hadden@14 PHRASE 59 dan@15 PHRASE 59 waren@16 PHRASE 59 ze@17 PHRASE 59 tot@18 PHRASE 59 de@19 PHRASE 59 conclusie@20 PHRASE 59 gekomen@21 PHRASE 59 dat@22 PHRASE 59 performance@23 PHRASE 59 af@24 PHRASE 59 dus@25 PHRASE 59 bilinear@26 PHRASE 59 af@27 PHRASE 59 op@28 PHRASE 59 de@29 PHRASE 59 9700pro@30 PHRASE 59 goed@31 PHRASE 59 te@32 PHRASE 59 vergelijken@33 PHRASE 59 is@34 PHRASE 59 met@35 PHRASE 59 balanced@36 PHRASE 59 af@37 PHRASE 59 op@38 PHRASE 59 de@39 PHRASE 59 gffx@40 PHRASE 59 en@41 PHRASE 59 dan@42 PHRASE 59 hadden@43 PHRASE 59 ze@44 PHRASE 59 ook@45 PHRASE 59 gezien@46 PHRASE 59 dat@47 PHRASE 59 de@48 PHRASE 59 gffx@49 PHRASE 59 niet@50 PHRASE 59 kan@51 PHRASE 59 tippen@52 PHRASE 59 aan@53 PHRASE 59 de@54 PHRASE 59 quality@55 PHRASE 59 af@56 PHRASE 59 van@57 PHRASE 59 de@58 PHRASE 59 9700pro@59)" },
289 { "$structure = imap_header($mbox, $tt);", "(Zstructur@1 OR imap_header@2 OR Zmbox@3 OR Ztt@4)" },
290 { "\"ifup: Could not get a valid interface name: -> skipped\"", "(ifup@1 PHRASE 9 could@2 PHRASE 9 not@3 PHRASE 9 get@4 PHRASE 9 a@5 PHRASE 9 valid@6 PHRASE 9 interface@7 PHRASE 9 name@8 PHRASE 9 skipped@9)" },
291 { "Er kan geen combinatie van filters worden gevonden om de gegevensstroom te genereren. (Error=80040218)", "(er@1 OR Zkan@2 OR Zgeen@3 OR Zcombinati@4 OR Zvan@5 OR Zfilter@6 OR Zworden@7 OR Zgevonden@8 OR Zom@9 OR Zde@10 OR Zgegevensstroom@11 OR Zte@12 OR Zgenereren@13 OR (error@14 OR 80040218@15))" },
292 { "ereg_replace(\"\\\\\",\"\\/\"", "ereg_replace@1" },
293 { "\\\\\"divx+geen+geluid\\\\\"", "(divx@1 PHRASE 3 geen@2 PHRASE 3 geluid@3)" },
294 { "lcase(\"string\")", "(lcase@1 OR string@2)" },
295 { "isEmpty( ) functie in visual basic", "(isempty@1 OR (Zfuncti@2 OR Zin@3 OR Zvisual@4 OR Zbasic@5))" },
296 { "*** stop: 0x0000001E (0xC0000005,0x00000000,0x00000000,0x00000000)", "(Zstop@1 OR 0x0000001e@2 OR 0xc0000005,0x00000000,0x00000000,0x00000000@3)" },
297 { "\"ctrl+v+c+a fout\"", "(ctrl@1 PHRASE 5 v@2 PHRASE 5 c@3 PHRASE 5 a@4 PHRASE 5 fout@5)" },
298 { "Server.CreateObject(\"ADODB.connection\")", "((server@1 PHRASE 2 createobject@2) OR (adodb@3 PHRASE 2 connection@4))" },
299 { "Presario 6277EA-XP model P4/28 GHz-120GB-DVD-CDRW (512MBWXP) (470048-012)", "(presario@1 OR (6277ea@2 PHRASE 2 xp@3) OR Zmodel@4 OR (p4@5 PHRASE 2 28@6) OR (ghz@7 PHRASE 4 120gb@8 PHRASE 4 dvd@9 PHRASE 4 cdrw@10) OR 512mbwxp@11 OR (470048@12 PHRASE 2 012@13))" },
300 { "Failed to connect agent. (AGENT=dbaxchg2, EC=UserId =NUll)", "(failed@1 OR Zto@2 OR Zconnect@3 OR Zagent@4 OR (agent@5 OR Zdbaxchg2@6 OR ec@7 OR userid@8 OR null@9))" },
301 { "delphi CreateOleObject(\"MSXML2.DomDocument\")", "(Zdelphi@1 OR createoleobject@2 OR (msxml2@3 PHRASE 2 domdocument@4))" },
302 { "Unhandled exeption in IEXPLORE.EXE (FTAPP.DLL)", "(unhandled@1 OR Zexept@2 OR Zin@3 OR (iexplore@4 PHRASE 2 exe@5) OR (ftapp@6 PHRASE 2 dll@7))" },
303 { "IBM High Rate Wireless LAN PCI Adapter (Low Profile Enabled)", "(ibm@1 OR high@2 OR rate@3 OR wireless@4 OR lan@5 OR pci@6 OR adapter@7 OR (low@8 OR profile@9 OR enabled@10))" },
304 { "asp ' en \"", "(Zasp@1 OR Zen@2)" },
305 { "Hercules 3D Prophet 8500 LE 64MB (OEM, Radeon 8500 LE)", "(hercules@1 OR 3d@2 OR prophet@3 OR 8500@4 OR le@5 OR 64mb@6 OR (oem@7 OR (radeon@8 OR 8500@9 OR le@10)))" },
306 { "session_set_cookie_params(echo \"hoi\")", "(session_set_cookie_params@1 OR Zecho@2 OR hoi@3)" },
307 { "windows update werkt niet (windows se", "(Zwindow@1 OR Zupdat@2 OR Zwerkt@3 OR Zniet@4 OR (Zwindow@5 OR Zse@6))" },
308 { "De statuscode van de fout is ( 0 x 4 , 0 , 0 , 0 )", "(de@1 OR Zstatuscod@2 OR Zvan@3 OR Zde@4 OR Zfout@5 OR Zis@6 OR (0@7 OR Zx@8 OR 4@9 OR 0@10 OR 0@11 OR 0@12))" },
309 { "sony +(u20 u-20)", "((Zu20@2 OR (u@3 PHRASE 2 20@4)) AND_MAYBE Zsoni@1)" },
310 { "[crit] (17)File exists: unable to create scoreboard (name-based shared memory failure)", "(Zcrit@1 OR 17@2 OR (file@3 OR Zexist@4 OR Zunabl@5 OR Zto@6 OR Zcreat@7 OR Zscoreboard@8) OR ((name@9 PHRASE 2 based@10) OR (Zshare@11 OR Zmemori@12 OR Zfailur@13)))" },
311 { "directories lokaal php (uitlezen OR inladen)", "(Zdirectori@1 OR Zlokaal@2 OR Zphp@3 OR (Zuitlezen@4 OR Zinladen@5))" },
312 { "(multi pc modem)+ (line sync)", "(Zmulti@1 OR Zpc@2 OR Zmodem@3 OR (Zline@4 OR Zsync@5))" },
313 { "xp 5.1.2600.0 (xpclient.010817-1148)", "(Zxp@1 OR 5.1.2600.0@2 OR (xpclient@3 PHRASE 3 010817@4 PHRASE 3 1148@5))" },
314 { "DirectDraw test results: Failure at step 5 (User verification of rectangles): HRESULT = 0x00000000 (error code) Direct3D 7 test results: Failure at step 32 (User verification of Direct3D rendering): HRESULT = 0x00000000 (error code) Direct3D 8 test results: Failure at step 32 (User verification of Direct3D rendering): HRESULT = 0x00000000 (error code) Direct3D 9 test results: Failure at step 32 (User verification of Direct3D rendering): HRESULT = 0x00000000 (error code)", "(directdraw@1 OR Ztest@2 OR Zresult@3 OR failure@4 OR Zat@5 OR Zstep@6 OR 5@7 OR (user@8 OR Zverif@9 OR Zof@10 OR Zrectangl@11) OR hresult@12 OR 0x00000000@13 OR (Zerror@14 OR Zcode@15) OR (direct3d@16 OR 7@17 OR Ztest@18 OR Zresult@19 OR failure@20 OR Zat@21 OR Zstep@22 OR 32@23) OR (user@24 OR Zverif@25 OR Zof@26 OR direct3d@27 OR Zrender@28) OR hresult@29 OR 0x00000000@30 OR (Zerror@31 OR Zcode@32) OR (direct3d@33 OR 8@34 OR Ztest@35 OR Zresult@36 OR failure@37 OR Zat@38 OR Zstep@39 OR 32@40) OR (user@41 OR Zverif@42 OR Zof@43 OR direct3d@44 OR Zrender@45) OR hresult@46 OR 0x00000000@47 OR (Zerror@48 OR Zcode@49) OR (direct3d@50 OR 9@51 OR Ztest@52 OR Zresult@53 OR failure@54 OR Zat@55 OR Zstep@56 OR 32@57) OR (user@58 OR Zverif@59 OR Zof@60 OR direct3d@61 OR Zrender@62) OR hresult@63 OR 0x00000000@64 OR (Zerror@65 OR Zcode@66))" },
315 { "Thermaltake Aquarius II waterkoeling (kompleet voor P4 en XP)", "(thermaltake@1 OR aquarius@2 OR ii@3 OR Zwaterkoel@4 OR (Zkompleet@5 OR Zvoor@6 OR p4@7 OR Zen@8 OR xp@9))" },
316 { "E3501 unable to add job to database (EC=-2005)", "(e3501@1 OR Zunabl@2 OR Zto@3 OR Zadd@4 OR Zjob@5 OR Zto@6 OR Zdatabas@7 OR (ec@8 OR 2005@9))" },
317 { "\"arp -s\" ip veranderen", "((arp@1 PHRASE 2 s@2) OR (Zip@3 OR Zveranderen@4))" },
318 { "header(\"content-type: application/octet-stream\");", "(header@1 OR (content@2 PHRASE 2 type@3) OR (application@4 PHRASE 3 octet@5 PHRASE 3 stream@6))" },
319 { "$datum = date(\"d-m-Y\");", "(Zdatum@1 OR date@2 OR (d@3 PHRASE 3 m@4 PHRASE 3 y@5))" },
320 { "\"'\" +asp", "Zasp@1" },
321 { "+session +[", "Zsession@1" },
322 { "Dit apparaat kan niet starten. (Code 10)", "(dit@1 OR Zapparaat@2 OR Zkan@3 OR Zniet@4 OR Zstarten@5 OR (code@6 OR 10@7))" },
323 { "\"You cannot use the Administration program while the Domino Server is running. Either shut down the Domino Server (but keep the file server running) or choose the ican labeled 'Lotus Notes' instead.\"", "(you@1 PHRASE 32 cannot@2 PHRASE 32 use@3 PHRASE 32 the@4 PHRASE 32 administration@5 PHRASE 32 program@6 PHRASE 32 while@7 PHRASE 32 the@8 PHRASE 32 domino@9 PHRASE 32 server@10 PHRASE 32 is@11 PHRASE 32 running@12 PHRASE 32 either@13 PHRASE 32 shut@14 PHRASE 32 down@15 PHRASE 32 the@16 PHRASE 32 domino@17 PHRASE 32 server@18 PHRASE 32 but@19 PHRASE 32 keep@20 PHRASE 32 the@21 PHRASE 32 file@22 PHRASE 32 server@23 PHRASE 32 running@24 PHRASE 32 or@25 PHRASE 32 choose@26 PHRASE 32 the@27 PHRASE 32 ican@28 PHRASE 32 labeled@29 PHRASE 32 lotus@30 PHRASE 32 notes@31 PHRASE 32 instead@32)" },
324 { "\"+irq +veranderen +xp\"", "(irq@1 PHRASE 3 veranderen@2 PHRASE 3 xp@3)" },
325 { "\"is not a member of 'operator``global namespace''' + c++", "(is@1 PHRASE 9 not@2 PHRASE 9 a@3 PHRASE 9 member@4 PHRASE 9 of@5 PHRASE 9 operator@6 PHRASE 9 global@7 PHRASE 9 namespace@8 PHRASE 9 c++@9)" },
326 { "mkdir() failed (File exists) php", "(mkdir@1 OR Zfail@2 OR (file@3 OR Zexist@4) OR Zphp@5)" },
327 { "laatsteIndex(int n)", "(laatsteindex@1 OR (Zint@2 OR Zn@3))" },
328 { "\"line+in\" OR \"c8783\"", "((line@1 PHRASE 2 in@2) OR c8783@3)" },
329 { "if ($_POST['Submit'])", "(Zif@1 OR (_post@2 OR submit@3))" },
330 { "NEC DVD+-RW ND-1300A", "(nec@1 OR (dvd+@2 PHRASE 2 rw@3) OR (nd@4 PHRASE 2 1300a@5))" },
331 { "*String not found* (*String not found*.)", "(string@1 OR Znot@2 OR found@3 OR (string@4 OR Znot@5 OR found@6))" },
332 { "MSI G4Ti4200-TD 128MB (GeForce4 Ti4200)", "(msi@1 OR (g4ti4200@2 PHRASE 2 td@3) OR 128mb@4 OR (geforce4@5 OR ti4200@6))" },
333 { "href=\"#\"", "href@1" },
334 { "Request.ServerVariables(\"REMOTE_USER\") javascript", "((request@1 PHRASE 2 servervariables@2) OR remote_user@3 OR Zjavascript@4)" },
335 { "XF86Config(-4) waar", "(xf86config@1 OR 4@2 OR Zwaar@3)" },
336 { "Unknown (tag 2000)", "(unknown@1 OR (Ztag@2 OR 2000@3))" },
337 { "KT4V(MS-6712)", "(kt4v@1 OR (ms@2 PHRASE 2 6712@3))" },
338 { "scheduled+AND+nieuwsgroepen+AND+updaten", "(Zschedul@1 AND Znieuwsgroepen@2 AND Zupdaten@3)" },
339 { "137(netbios-ns)", "(137@1 OR (netbios@2 PHRASE 2 ns@3))" },
340 { "HARWARE ERROR, TRACKING SERVO (4:0X09:0X01)", "(harware@1 OR error@2 OR (tracking@3 OR servo@4) OR (4@5 PHRASE 3 0x09@6 PHRASE 3 0x01@7))" },
341 { "Chr(10) wat is code van \" teken", "(chr@1 OR 10@2 OR (Zwat@3 OR Zis@4 OR Zcode@5 OR Zvan@6) OR Zteken@7)" },
342 { "wat is code van \" teken", "(Zwat@1 OR Zis@2 OR Zcode@3 OR Zvan@4 OR teken@5)" },
343 { "The Jet VBA file (VBAJET.dll for 16-bit version, VBAJET32.dll version", "(the@1 OR jet@2 OR vba@3 OR Zfile@4 OR ((vbajet@5 PHRASE 2 dll@6) OR Zfor@7 OR (16@8 PHRASE 2 bit@9) OR Zversion@10 OR (vbajet32@11 PHRASE 2 dll@12) OR Zversion@13))" },
344 { "Permission denied (publickey,password,keyboard-interactive).", "(permission@1 OR Zdeni@2 OR (Zpublickey@3 OR Zpassword@4 OR (keyboard@5 PHRASE 2 interactive@6)))" },
345 { "De lees- of schrijfbewerking (\"written\") op het geheugen is mislukt", "(de@1 OR Zlee@2 OR Zof@3 OR Zschrijfbewerk@4 OR written@5 OR (Zop@6 OR Zhet@7 OR Zgeheugen@8 OR Zis@9 OR Zmislukt@10))" },
346 { "Primary IDE channel no 80 conductor cable installed\"", "(primary@1 OR ide@2 OR Zchannel@3 OR Zno@4 OR 80@5 OR Zconductor@6 OR Zcabl@7 OR installed@8)" },
347 { "\"2020 NEAR zoom\"", "(2020@1 PHRASE 3 near@2 PHRASE 3 zoom@3)" },
348 { "setcookie(\"naam\",\"$user\");", "(setcookie@1 OR naam@2 OR user@3)" },
349 { "MSI 645 Ultra (MS-6547) Ver1", "(msi@1 OR 645@2 OR ultra@3 OR (ms@4 PHRASE 2 6547@5) OR ver1@6)" },
350 { "if ($HTTP", "(Zif@1 OR http@2)" },
351 { "data error(cyclic redundancy check)", "(Zdata@1 OR error@2 OR (Zcyclic@3 OR Zredund@4 OR Zcheck@5))" },
352 { "UObject::StaticAllocateObject <- (NULL None) <- UObject::StaticConstructObject <- InitEngine", "((uobject@1 PHRASE 2 staticallocateobject@2) OR (null@3 OR none@4) OR (uobject@5 PHRASE 2 staticconstructobject@6) OR initengine@7)" },
353 { "Failure at step 8 (Creating 3D Device)", "(failure@1 OR Zat@2 OR Zstep@3 OR 8@4 OR (creating@5 OR 3d@6 OR device@7))" },
354 { "Call Shell(\"notepad.exe\",", "(call@1 OR shell@2 OR (notepad@3 PHRASE 2 exe@4))" },
355 { "2.5\" harddisk converter", "(2.5@1 OR (harddisk@2 PHRASE 2 converter@3))" },
356 { "creative labs \"dvd+rw\"", "(Zcreativ@1 OR Zlab@2 OR (dvd@3 PHRASE 2 rw@4))" },
357 { "\"het beleid van deze computer staat u niet toe interactief", "(het@1 PHRASE 10 beleid@2 PHRASE 10 van@3 PHRASE 10 deze@4 PHRASE 10 computer@5 PHRASE 10 staat@6 PHRASE 10 u@7 PHRASE 10 niet@8 PHRASE 10 toe@9 PHRASE 10 interactief@10)" },
358 { "ati radeon \"driver cleaner", "(Zati@1 OR Zradeon@2 OR (driver@3 PHRASE 2 cleaner@4))" },
359 { "\"../\" path", "Zpath@1" },
360 { "(novell client) workstation only", "(Znovel@1 OR Zclient@2 OR (Zworkstat@3 OR Zonli@4))" },
361 { "Unable to find libgd.(a|so) anywhere", "(unable@1 OR Zto@2 OR Zfind@3 OR Zlibgd@4 OR Za@5 OR Zso@6 OR Zanywher@7)" },
362 { "\"libstdc++-libc6.1-1.so.2\"", "(libstdc++@1 PHRASE 5 libc6.1@2 PHRASE 5 1@3 PHRASE 5 so@4 PHRASE 5 2@5)" },
363 { "ipsec_setup (/etc/ipsec.conf, line 1) cannot open configuration file \"/etc/ipsec.conf\" -- `' aborted", "(Zipsec_setup@1 OR ((etc@2 PHRASE 3 ipsec@3 PHRASE 3 conf@4) OR (Zline@5 OR 1@6)) OR (Zcannot@7 OR Zopen@8 OR Zconfigur@9 OR Zfile@10) OR (etc@11 PHRASE 3 ipsec@12 PHRASE 3 conf@13) OR Zabort@14)" },
364 { "Forwarden van domeinnaam (naar HTTP adres)", "(forwarden@1 OR Zvan@2 OR Zdomeinnaam@3 OR (Znaar@4 OR http@5 OR Zadr@6))" },
365 { "Compaq HP, 146.8 GB (MPN-286716-B22) Hard Drives", "(compaq@1 OR hp@2 OR (146.8@3 OR gb@4) OR (mpn@5 PHRASE 3 286716@6 PHRASE 3 b22@7) OR (hard@8 OR drives@9))" },
366 { "httpd (no pid file) not running", "(Zhttpd@1 OR (Zno@2 OR Zpid@3 OR Zfile@4) OR (Znot@5 OR Zrun@6))" },
367 { "apache httpd (pid file) not running", "(Zapach@1 OR Zhttpd@2 OR (Zpid@3 OR Zfile@4) OR (Znot@5 OR Zrun@6))" },
368 { "Klasse is niet geregistreerd (Fout=80040154).", "(klasse@1 OR Zis@2 OR Zniet@3 OR Zgeregistreerd@4 OR (fout@5 OR 80040154@6))" },
369 { "\"dvd+r\" \"dvd-r\"", "((dvd@1 PHRASE 2 r@2) OR (dvd@3 PHRASE 2 r@4))" },
370 { "\"=\" tekens uit csvfile", "(Zteken@1 OR Zuit@2 OR Zcsvfile@3)" },
371 { "libc.so.6(GLIBC_2.3)", "((libc@1 PHRASE 3 so@2 PHRASE 3 6@3) OR glibc_2.3@4)" },
372 { "Sitecom Broadband xDSL / Cable Router 4S (DC-202)", "(sitecom@1 OR broadband@2 OR Zxdsl@3 OR (cable@4 OR router@5 OR 4s@6) OR (dc@7 PHRASE 2 202@8))" },
373 { "(t-mobile) bereik", "((t@1 PHRASE 2 mobile@2) OR Zbereik@3)" },
374 { "error LNK2001: unresolved external symbol \"public", "(Zerror@1 OR lnk2001@2 OR Zunresolv@3 OR Zextern@4 OR Zsymbol@5 OR public@6)" },
375 { "patch linux exploit -p)", "(Zpatch@1 OR Zlinux@2 OR Zexploit@3 OR Zp@4)" },
376 { "MYD not found (Errcode: 2)", "(myd@1 OR Znot@2 OR Zfound@3 OR (errcode@4 OR 2@5))" },
377 { "ob_start(\"ob_gzhandler\"); file download", "(ob_start@1 OR ob_gzhandler@2 OR (Zfile@3 OR Zdownload@4))" },
378 { "ECS Elitegroup K7VZA (VIA VT8363/VT8363A)", "(ecs@1 OR elitegroup@2 OR k7vza@3 OR (via@4 OR (vt8363@5 PHRASE 2 vt8363a@6)))" },
379 { "ASUS A7V8X (LAN + Serial-ATA + Firewire + Raid + Audio)", "(asus@1 OR a7v8x@2 OR (lan@3 OR (serial@4 PHRASE 2 ata@5) OR firewire@6 OR raid@7 OR audio@8))" },
380 { "Javascript:history.go(-1)", "((javascript@1 PHRASE 3 history@2 PHRASE 3 go@3) OR 1@4)" },
381 { "java :) als icon", "(Zjava@1 OR (Zal@2 OR Zicon@3))" },
382 { "onmouseover=setPointer(this", "(onmouseover@1 OR setpointer@2 OR Zthis@3)" },
383 { "\" in vbscript", "(in@1 PHRASE 2 vbscript@2)" },
384 { "IRC (FAQ OR (hulp NEAR bij))", "(irc@1 OR (faq@2 OR (hulp@3 NEAR 11 bij@4)))" },
385 { "setProperty(\"McSquare\"+i, _xscale, _xscale++);", "(setproperty@1 OR mcsquare@2 OR Zi@3 OR _xscale@4 OR _xscale++@5)" },
386 { "[warn] Apache does not support line-end comments. Consider using quotes around argument: \"#-1\"", "(Zwarn@1 OR (apache@2 OR Zdoe@3 OR Znot@4 OR Zsupport@5) OR (line@6 PHRASE 2 end@7) OR Zcomment@8 OR (consider@9 OR Zuse@10 OR Zquot@11 OR Zaround@12 OR Zargument@13) OR 1@14)" },
387 { "(php.ini) (memory_limit)", "((php@1 PHRASE 2 ini@2) OR Zmemory_limit@3)" },
388 { "line 8: syntax error near unexpected token `kernel_thread(f'", "(Zline@1 OR 8@2 OR Zsyntax@3 OR Zerror@4 OR Znear@5 OR Zunexpect@6 OR Ztoken@7 OR kernel_thread@8 OR Zf@9)" },
389 { "VXD NAVEX()@)", "(vxd@1 OR navex@2)" },
390 { "\"Iiyama AS4314UT 17\" \"", "(iiyama@1 PHRASE 3 as4314ut@2 PHRASE 3 17@3)" },
391 { "include (\"$id.html\");", "(Zinclud@1 OR (id@2 PHRASE 2 html@3))" },
392 { "include id.Today's date is: <? print (date (\"M d, Y\")); ?>hp", "(Zinclud@1 OR (id@2 PHRASE 2 today's@3) OR (Zdate@4 OR Zis@5) OR Zprint@6 OR (Zdate@7 OR (m@8 PHRASE 3 d@9 PHRASE 3 y@10)) OR Zhp@11)" },
393 { "(program files\\common) opstarten", "(Zprogram@1 OR (files@2 PHRASE 2 common@3) OR Zopstarten@4)" },
394 { "java \" string", "(Zjava@1 OR string@2)" },
395 { "+=", "" },
396 { "php +=", "Zphp@1" },
397 { "[php] ereg_replace(\".\"", "(Zphp@1 OR ereg_replace@2)" },
398 { "\"echo -e\" kleur", "((echo@1 PHRASE 2 e@2) OR Zkleur@3)" },
399 { "adobe premiere \"-1\"", "(Zadob@1 OR Zpremier@2 OR 1@3)" },
400 { "DVD brander \"+\" en \"-\"", "(dvd@1 OR Zbrander@2 OR Zen@3)" },
401 { "inspirion \"dvd+R\"", "(Zinspirion@1 OR (dvd@2 PHRASE 2 r@3))" },
402 { "asp 0x80040E14)", "(Zasp@1 OR 0x80040e14@2)" },
403 { "\"e-tech motorola router", "(e@1 PHRASE 4 tech@2 PHRASE 4 motorola@3 PHRASE 4 router@4)" },
404 { "bluetooth '1.3.2.19\"", "(Zbluetooth@1 OR 1.3.2.19@2)" },
405 { "ms +-connect", "(Zms@1 OR Zconnect@2)" },
406 { "php+print+\"", "(Zphp@1 OR print+@2)" },
407 { "athlon 1400 :welke videokaart\"", "(Zathlon@1 OR 1400@2 OR (Zwelk@3 OR videokaart@4))" },
408 { "+-dvd", "Zdvd@1" },
409 { "glftpd \"-new-\"", "(Zglftpd@1 OR new@2)" },
410 { "\"scandisk + dos5.0", "(scandisk@1 PHRASE 2 dos5.0@2)" },
411 { "socket\\(\\)", "socket@1" },
412 { "msn (e-tech) router", "(Zmsn@1 OR (e@2 PHRASE 2 tech@3) OR Zrouter@4)" },
413 { "Het grote Epox 8k3a+ ervaring/prob topic\"", "(het@1 OR Zgrote@2 OR epox@3 OR 8k3a+@4 OR (ervaring@5 PHRASE 2 prob@6) OR topic@7)" },
414 { "\"CF+bluetooth\"", "(cf@1 PHRASE 2 bluetooth@2)" },
415 { "kwaliteit (s-video) composite verschil tv out", "(Zkwaliteit@1 OR (s@2 PHRASE 2 video@3) OR (Zcomposit@4 OR Zverschil@5 OR Ztv@6 OR Zout@7))" },
416 { "Wie kan deze oude hardware nog gebruiken\" Deel", "(wie@1 OR Zkan@2 OR Zdeze@3 OR Zoud@4 OR Zhardwar@5 OR Znog@6 OR gebruiken@7 OR deel@8)" },
417 { "Public Declare Sub Sleep Lib \"kernel32\" (ByVal dwMilliseconds As Long)", "(public@1 OR declare@2 OR sub@3 OR sleep@4 OR lib@5 OR kernel32@6 OR (byval@7 OR Zdwmillisecond@8 OR as@9 OR long@10))" },
418 { "for inclusion (include_path='.:/usr/share/php')", "(Zfor@1 OR Zinclus@2 OR (include_path@3 OR (usr@4 PHRASE 3 share@5 PHRASE 3 php@6)))" },
419 { "\"muziek 2x zo snel\"\"", "(muziek@1 PHRASE 4 2x@2 PHRASE 4 zo@3 PHRASE 4 snel@4)" },
420 { "execCommand('inserthorizontalrule'", "(execcommand@1 OR Zinserthorizontalrul@2)" },
421 { "specs: IBM PS/2, Intel 8086 @ 25 mhz!!, 2 mb intern, 50 mb hd, 5.5\" floppy drive, toetsenbord en geen muis", "(Zspec@1 OR ibm@2 OR (ps@3 PHRASE 2 2@4) OR (intel@5 OR 8086@6) OR (25@7 OR Zmhz@8) OR (2@9 OR Zmb@10 OR Zintern@11) OR (50@12 OR Zmb@13 OR Zhd@14) OR 5.5@15 OR (floppy@16 PHRASE 6 drive@17 PHRASE 6 toetsenbord@18 PHRASE 6 en@19 PHRASE 6 geen@20 PHRASE 6 muis@21))" },
422 { "History: GetEventTool <- GetMusicManager <- GetMusicScript <- DMCallRoutine <- AMusicScriptEvent::execCallRoutine <- UObject::execClassContext <- (U2GameInfo M08A1.U2GameInfo0 @ Function U2.U2GameInfo.NotifyLevelChangeEnd : 0075 line 744) <- UObject::ProcessEvent <- (U2GameInfo M08A1.U2GameInfo0, Function U2.U2GameInfo.NotifyLevelChangeEnd) <- UGameEngine::LoadMap <- LocalMapURL <- UGameEngine::Browse <- ServerTravel <- UGameEngine::Tick <- UpdateWorld <- MainLoop", "(history@1 OR geteventtool@2 OR getmusicmanager@3 OR getmusicscript@4 OR dmcallroutine@5 OR (amusicscriptevent@6 PHRASE 2 execcallroutine@7) OR (uobject@8 PHRASE 2 execclasscontext@9) OR (u2gameinfo@10 OR (m08a1@11 PHRASE 2 u2gameinfo0@12) OR function@13 OR (u2@14 PHRASE 3 u2gameinfo@15 PHRASE 3 notifylevelchangeend@16) OR (0075@17 OR Zline@18 OR 744@19)) OR (uobject@20 PHRASE 2 processevent@21) OR (u2gameinfo@22 OR (m08a1@23 PHRASE 2 u2gameinfo0@24) OR function@25 OR (u2@26 PHRASE 3 u2gameinfo@27 PHRASE 3 notifylevelchangeend@28)) OR (ugameengine@29 PHRASE 2 loadmap@30) OR localmapurl@31 OR (ugameengine@32 PHRASE 2 browse@33) OR servertravel@34 OR (ugameengine@35 PHRASE 2 tick@36) OR updateworld@37 OR mainloop@38)" },
423 { "Support AMD XP 2400+ & 2600+ (K7T Turbo2 only)", "(support@1 OR amd@2 OR xp@3 OR 2400+@4 OR 2600+@5 OR (k7t@6 OR turbo2@7 OR Zonli@8))" },
424 { "'\"><br>bla</br>", "(br@1 PHRASE 3 bla@2 PHRASE 3 br@3)" },
425 { "The instruction at \"0x30053409\" referenced memory at \"0x06460504\". The memory could not be \"read'. Click OK to terminate the application.", "(the@1 OR Zinstruct@2 OR Zat@3 OR 0x30053409@4 OR (Zreferenc@5 OR Zmemori@6 OR Zat@7) OR 0x06460504@8 OR (the@9 OR Zmemori@10 OR Zcould@11 OR Znot@12 OR Zbe@13) OR (read@14 PHRASE 7 click@15 PHRASE 7 ok@16 PHRASE 7 to@17 PHRASE 7 terminate@18 PHRASE 7 the@19 PHRASE 7 application@20))" },
426 { "\"(P5A-b)\"", "(p5a@1 PHRASE 2 b@2)" },
427 { "(13,5 > 13) == no-go!", "(13,5@1 OR 13@2 OR (no@3 PHRASE 2 go@4))" },
428 { "eth not found \"ifconfig -a\"", "(Zeth@1 OR Znot@2 OR Zfound@3 OR (ifconfig@4 PHRASE 2 a@5))" },
429 { "<META NAME=\"ROBOTS", "(meta@1 OR name@2 OR robots@3)" },
430 { "lp0: using parport0 (interrupt-driven)", "(Zlp0@1 OR (Zuse@2 OR Zparport0@3) OR (interrupt@4 PHRASE 2 driven@5))" },
431 { "ULTRA PC-TUNING, COOLING & MODDING (4,6)", "(ultra@1 OR (pc@2 PHRASE 2 tuning@3) OR cooling@4 OR modding@5 OR 4,6@6)" },
432 { "512MB PC2700 DDR SDRAM Rood (Dane-Elec)", "(512mb@1 OR pc2700@2 OR ddr@3 OR sdram@4 OR rood@5 OR (dane@6 PHRASE 2 elec@7))" },
433 { "header(\"Content Type: text/html\");", "(header@1 OR (content@2 OR type@3) OR (text@4 PHRASE 2 html@5))" },
434 { "\"-RW\" \"+RW\"", "(rw@1 OR rw@2)" },
435 { "\"cresta digital answering machine", "(cresta@1 PHRASE 4 digital@2 PHRASE 4 answering@3 PHRASE 4 machine@4)" },
436 { "Arctic Super Silent PRO TC (Athlon/P3 - 2,3 GHz)", "(arctic@1 OR super@2 OR silent@3 OR pro@4 OR tc@5 OR ((athlon@6 PHRASE 2 p3@7) OR (2,3@8 OR ghz@9)))" },
437 { "c++ fopen \"r+t\"", "(Zc++@1 OR Zfopen@2 OR (r@3 PHRASE 2 t@4))" },
438 { "c++ fopen (r+t)", "(Zc++@1 OR Zfopen@2 OR (Zr@3 OR Zt@4))" },
439 { "\"DVD+R\"", "(dvd@1 PHRASE 2 r@2)" },
440 { "Class.forName(\"jdbc.odbc.JdbcOdbcDriver\");", "((class@1 PHRASE 2 forname@2) OR (jdbc@3 PHRASE 3 odbc@4 PHRASE 3 jdbcodbcdriver@5))" },
441 { "perl(find.pl)", "(perl@1 OR (find@2 PHRASE 2 pl@3))" },
442 { "\"-5v\" voeding", "(5v@1 OR Zvoed@2)" },
443 { "\"-5v\" power supply", "(5v@1 OR (Zpower@2 OR Zsuppli@3))" },
444 { "An Error occurred whie attempting to initialize the Borland Database Engine (error $2108)", "(an@1 OR error@2 OR Zoccur@3 OR Zwhie@4 OR Zattempt@5 OR Zto@6 OR Ziniti@7 OR Zthe@8 OR borland@9 OR database@10 OR engine@11 OR (Zerror@12 OR 2108@13))" },
445 { "(error $2108) Borland", "(Zerror@1 OR 2108@2 OR borland@3)" },
446 { "On Friday 04 April 2003 09:32, Edwin van Eersel wrote: > ik voel me eigenlijk wel behoorlijk kut :)", "(on@1 OR friday@2 OR 04@3 OR april@4 OR 2003@5 OR (09@6 PHRASE 2 32@7) OR (edwin@8 OR Zvan@9 OR eersel@10 OR Zwrote@11) OR (Zik@12 OR Zvoel@13 OR Zme@14 OR Zeigenlijk@15 OR Zwel@16 OR Zbehoorlijk@17 OR Zkut@18))" },
447 { "Elektrotechniek + \"hoe bevalt het?\"\"", "(elektrotechniek@1 OR (hoe@2 PHRASE 3 bevalt@3 PHRASE 3 het@4))" },
448 { "Shortcuts in menu (java", "(shortcuts@1 OR Zin@2 OR Zmenu@3 OR Zjava@4)" },
449 { "detonator+settings\"", "(Zdeton@1 OR settings@2)" },
450 { "(ez-bios) convert", "((ez@1 PHRASE 2 bios@2) OR Zconvert@3)" },
451 { "Sparkle 7100M4 64MB (GeForce4 MX440)", "(sparkle@1 OR 7100m4@2 OR 64mb@3 OR (geforce4@4 OR mx440@5))" },
452 { "freebsd \"boek OR newbie\"", "(Zfreebsd@1 OR (boek@2 PHRASE 3 or@3 PHRASE 3 newbie@4))" },
453 { "for (;;) c++", "(Zfor@1 OR Zc++@2)" },
454 { "1700+-2100+", "(1700+@1 PHRASE 2 2100+@2)" },
455 { "PHP Warning: Invalid library (maybe not a PHP library) 'libmysqlclient.so'", "(php@1 OR warning@2 OR invalid@3 OR Zlibrari@4 OR (Zmayb@5 OR Znot@6 OR Za@7 OR php@8 OR Zlibrari@9) OR (libmysqlclient@10 PHRASE 2 so@11))" },
456 { "NEC DV-5800B (Bul", "(nec@1 OR (dv@2 PHRASE 2 5800b@3) OR bul@4)" },
457 { "org.jdom.input.SAXBuilder.<init>(SAXBuilder.java)", "((org@1 PHRASE 4 jdom@2 PHRASE 4 input@3 PHRASE 4 saxbuilder@4) OR init@5 OR (saxbuilder@6 PHRASE 2 java@7))" },
458 { "AMD Athlon XP 2500+ (1,83GHz, 512KB)", "(amd@1 OR athlon@2 OR xp@3 OR 2500+@4 OR (1,83ghz@5 OR 512kb@6))" },
459 { "'q ben\"", "(Zq@1 OR ben@2)" },
460 { "getsmbfilepwent: malformed password entry (uid not number)", "(Zgetsmbfilepw@1 OR (Zmalform@2 OR Zpassword@3 OR Zentri@4) OR (Zuid@5 OR Znot@6 OR Znumber@7))" },
461 { "\xc3\xb6ude onderdelen\"", "(Z\xc3\xb6ude@1 OR onderdelen@2)" },
462 { "Heeft iemand enig idee waarom de pioneer (zelf met originele firmware van pioneer) bij mij niet wil flashen ??", "(heeft@1 OR Ziemand@2 OR Zenig@3 OR Zide@4 OR Zwaarom@5 OR Zde@6 OR Zpioneer@7 OR (Zzelf@8 OR Zmet@9 OR Zoriginel@10 OR Zfirmwar@11 OR Zvan@12 OR Zpioneer@13) OR (Zbij@14 OR Zmij@15 OR Zniet@16 OR Zwil@17 OR Zflashen@18))" },
463 { "asus a7v266 bios nieuw -(a7v266-e)", "((Zasus@1 OR Za7v266@2 OR Zbio@3 OR Znieuw@4) AND_NOT (a7v266@5 PHRASE 2 e@6))" },
464 { "cybercom \"dvd+r\"", "(Zcybercom@1 OR (dvd@2 PHRASE 2 r@3))" },
465 { "AMD PCNET Family Ethernet Adapter (PCI-ISA)", "(amd@1 OR pcnet@2 OR family@3 OR ethernet@4 OR adapter@5 OR (pci@6 PHRASE 2 isa@7))" },
466 { "relais +/-", "Zrelai@1" },
467 { "formules (slepen OR doortrekken) excel", "(Zformul@1 OR (Zslepen@2 OR Zdoortrekken@3) OR Zexcel@4)" },
468 { "\"%English", "english@1" },
469 { "select max( mysql", "(Zselect@1 OR max@2 OR Zmysql@3)" },
470 { "leejow(saait", "(leejow@1 OR Zsaait@2)" },
471 { "'Windows 2000 Advanced Server\" netwerkverbinding valt steeds weg", "(windows@1 OR 2000@2 OR advanced@3 OR server@4 OR (netwerkverbinding@5 PHRASE 4 valt@6 PHRASE 4 steeds@7 PHRASE 4 weg@8))" },
472 { "K7T Turbo 2 (MS-6330)", "(k7t@1 OR turbo@2 OR 2@3 OR (ms@4 PHRASE 2 6330@5))" },
473 { "failed to receive data from the client agent. (ec=1)", "(Zfail@1 OR Zto@2 OR Zreceiv@3 OR Zdata@4 OR Zfrom@5 OR Zthe@6 OR Zclient@7 OR Zagent@8 OR (ec@9 OR 1@10))" },
474 { "\"cannot find -lz\"", "(cannot@1 PHRASE 3 find@2 PHRASE 3 lz@3)" },
475 { "undefined reference to `mysql_drop_db'\"", "(Zundefin@1 OR Zrefer@2 OR Zto@3 OR Zmysql_drop_db@4)" },
476 { "search form asp \"%'", "(Zsearch@1 OR Zform@2 OR Zasp@3)" },
477 { "(dvd+r) kwaliteit", "(Zdvd@1 OR Zr@2 OR Zkwaliteit@3)" },
478 { "Fatal error: Allowed memory size of 8388608 bytes exhausted (tried to allocate 35 bytes)", "(fatal@1 OR Zerror@2 OR allowed@3 OR Zmemori@4 OR Zsize@5 OR Zof@6 OR 8388608@7 OR Zbyte@8 OR Zexhaust@9 OR (Ztri@10 OR Zto@11 OR Zalloc@12 OR 35@13 OR Zbyte@14))" },
479 { "geluid (schokt OR hapert)", "(Zgeluid@1 OR (Zschokt@2 OR Zhapert@3))" },
480 { "Het wordt pas echt leuk als het hard staat!! >:)", "(het@1 OR Zwordt@2 OR Zpas@3 OR Zecht@4 OR Zleuk@5 OR Zal@6 OR Zhet@7 OR Zhard@8 OR Zstaat@9)" },
481 { "Uw configuratie bestand bevat instellingen (root zonder wachtwoord) die betrekking hebben tot de standaard MySQL account. Uw MySQL server draait met deze standaard waardes, en is open voor ongewilde toegang, het wordt dus aangeraden dit op te lossen", "(uw@1 OR Zconfigurati@2 OR Zbestand@3 OR Zbevat@4 OR Zinstellingen@5 OR (Zroot@6 OR Zzonder@7 OR Zwachtwoord@8) OR (Zdie@9 OR Zbetrekk@10 OR Zhebben@11 OR Ztot@12 OR Zde@13 OR Zstandaard@14 OR mysql@15 OR Zaccount@16 OR uw@17 OR mysql@18 OR Zserver@19 OR Zdraait@20 OR Zmet@21 OR Zdeze@22 OR Zstandaard@23 OR Zwaard@24) OR (Zen@25 OR Zis@26 OR Zopen@27 OR Zvoor@28 OR Zongewild@29 OR Ztoegang@30) OR (Zhet@31 OR Zwordt@32 OR Zdus@33 OR Zaangeraden@34 OR Zdit@35 OR Zop@36 OR Zte@37 OR Zlossen@38))" },
482 { "(library qt-mt) not found", "(Zlibrari@1 OR (qt@2 PHRASE 2 mt@3) OR (Znot@4 OR Zfound@5))" },
483 { "Qt (>= Qt 3.0.3) (library qt-mt) not found", "(qt@1 OR (qt@2 OR 3.0.3@3) OR (Zlibrari@4 OR (qt@5 PHRASE 2 mt@6)) OR (Znot@7 OR Zfound@8))" },
484 { "setup was unable to find (or could not read) the language specific setup resource dll, unable to continue. Please reboot and try again.", "(Zsetup@1 OR Zwas@2 OR Zunabl@3 OR Zto@4 OR Zfind@5 OR (Zor@6 OR Zcould@7 OR Znot@8 OR Zread@9) OR (Zthe@10 OR Zlanguag@11 OR Zspecif@12 OR Zsetup@13 OR Zresourc@14 OR Zdll@15) OR (Zunabl@16 OR Zto@17 OR Zcontinu@18 OR please@19 OR Zreboot@20 OR Zand@21 OR Ztri@22 OR Zagain@23))" },
485 { "Titan TTC-D5TB(4/CU35)", "(titan@1 OR (ttc@2 PHRASE 2 d5tb@3) OR (4@4 PHRASE 2 cu35@5))" },
486 { "[php] date( min", "(Zphp@1 OR date@2 OR Zmin@3)" },
487 { "EPOX EP-8RDA+ (nForce2 SPP+MCP-T) Rev. 1.1", "(epox@1 OR (ep@2 PHRASE 2 8rda+@3) OR (Znforce2@4 OR spp@5 OR (mcp@6 PHRASE 2 t@7)) OR rev@8 OR 1.1@9)" },
488 { "554 5.4.6 Too many hops 53 (25 max)", "(554@1 OR 5.4.6@2 OR too@3 OR Zmani@4 OR Zhop@5 OR 53@6 OR (25@7 OR Zmax@8))" },
489 { "ik had toch nog een vraagje: er zijn nu eigenlijk alleen maar schijfjes van 4.7GB alleen straks zullen er vast schijfjes van meer dan 4.7GB komen. Zal deze brander dit wel kunnen schijven?""?(na bijvoorbeeld een firmware update?) ben erg benieuwd", "(Zik@1 OR Zhad@2 OR Ztoch@3 OR Znog@4 OR Zeen@5 OR Zvraagj@6 OR Zer@7 OR Zzijn@8 OR Znu@9 OR Zeigenlijk@10 OR Zalleen@11 OR Zmaar@12 OR Zschijfj@13 OR Zvan@14 OR 4.7gb@15 OR Zalleen@16 OR Zstrak@17 OR Zzullen@18 OR Zer@19 OR Zvast@20 OR Zschijfj@21 OR Zvan@22 OR Zmeer@23 OR Zdan@24 OR 4.7gb@25 OR Zkomen@26 OR zal@27 OR Zdeze@28 OR Zbrander@29 OR Zdit@30 OR Zwel@31 OR Zkunnen@32 OR Zschijven@33 OR (Zna@34 OR Zbijvoorbeeld@35 OR Zeen@36 OR Zfirmwar@37 OR Zupdat@38) OR (Zben@39 OR Zerg@40 OR Zbenieuwd@41))" },
490 { "ati linux drivers (4.3.0)", "(Zati@1 OR Zlinux@2 OR Zdriver@3 OR 4.3.0@4)" },
491 { "ENCAPSED_AND_WHITESPACE", "encapsed_and_whitespace@1" },
492 { "lpadmin: add-printer (set device) failed: client-error-not-possible", "(Zlpadmin@1 OR (add@2 PHRASE 2 printer@3) OR (Zset@4 OR Zdevic@5) OR Zfail@6 OR (client@7 PHRASE 4 error@8 PHRASE 4 not@9 PHRASE 4 possible@10))" },
493 { "welke dvd \"+r\" media", "(Zwelk@1 OR Zdvd@2 OR r@3 OR Zmedia@4)" },
494 { "Warning: stat failed for fotos(errno=2 - No such file or directory)", "(warning@1 OR (Zstat@2 OR Zfail@3 OR Zfor@4 OR fotos@5) OR errno@6 OR 2@7 OR (no@8 OR Zsuch@9 OR Zfile@10 OR Zor@11 OR Zdirectori@12))" },
495 { "dvd +/-", "Zdvd@1" },
496 { "7vaxp +voltage mod\"", "(Zvoltag@2 AND_MAYBE (7vaxp@1 OR mod@3))" },
497 { "lpt port (SPP/EPP) is enabled", "(Zlpt@1 OR Zport@2 OR (spp@3 PHRASE 2 epp@4) OR (Zis@5 OR Zenabl@6))" },
498 { "getenv(\"HTTP_REFERER\")", "(getenv@1 OR http_referer@2)" },
499 { "Error setting display mode: CreateDevice failed (D3DERR_DRIVERINTERNALERROR)", "(error@1 OR Zset@2 OR Zdisplay@3 OR Zmode@4 OR createdevice@5 OR Zfail@6 OR d3derr_driverinternalerror@7)" },
500 { "Exception number: c0000005 (access violation)", "(exception@1 OR Znumber@2 OR Zc0000005@3 OR (Zaccess@4 OR Zviolat@5))" },
501 { "header(\"Content-type:application/octetstream\");", "(header@1 OR (content@2 PHRASE 4 type@3 PHRASE 4 application@4 PHRASE 4 octetstream@5))" },
502 { "java.security.AccessControlException: access denied (java.lang.RuntimePermission accessClassInPackage.sun.jdbc.odbc)", "((java@1 PHRASE 3 security@2 PHRASE 3 accesscontrolexception@3) OR (Zaccess@4 OR Zdeni@5) OR ((java@6 PHRASE 3 lang@7 PHRASE 3 runtimepermission@8) OR (accessclassinpackage@9 PHRASE 4 sun@10 PHRASE 4 jdbc@11 PHRASE 4 odbc@12)))" },
503 { "(001.part.met", "(001@1 PHRASE 3 part@2 PHRASE 3 met@3)" },
504 { "Warning: mail(): Use the -f option (5th param) to include valid reply-to address ! in /usr/home/vdb/www/mail.php on line 79", "(warning@1 OR mail@2 OR (use@3 OR Zthe@4) OR (Zf@5 OR Zoption@6) OR (5th@7 OR Zparam@8) OR (Zto@9 OR Zinclud@10 OR Zvalid@11) OR (reply@12 PHRASE 2 to@13) OR Zaddress@14 OR Zin@15 OR (usr@16 PHRASE 6 home@17 PHRASE 6 vdb@18 PHRASE 6 www@19 PHRASE 6 mail@20 PHRASE 6 php@21) OR (Zon@22 OR Zline@23 OR 79@24))" },
505 { "PHP Use the -f option (5th param)", "((php@1 OR use@2 OR Zthe@3 OR Zoption@5 OR (5th@6 OR Zparam@7)) AND_NOT Zf@4)" },
506 { "dvd \"+\" \"-\"", "Zdvd@1" },
507 { "bericht ( %)", "Zbericht@1" },
508 { "2500+ of 2600+ (niett OC)", "(2500+@1 OR Zof@2 OR 2600+@3 OR (Zniett@4 OR oc@5))" },
509 { "maxtor windows xp werkt The drivers for this device are not installed. (Code 28)", "(Zmaxtor@1 OR Zwindow@2 OR Zxp@3 OR Zwerkt@4 OR the@5 OR Zdriver@6 OR Zfor@7 OR Zthis@8 OR Zdevic@9 OR Zare@10 OR Znot@11 OR Zinstal@12 OR (code@13 OR 28@14))" },
510 { "Warning: stat failed for /mnt/web/react/got/react/board/non-www/headlines/tnet-headlines.txt (errno=2 - No such file or directory) in /mnt/web/react/got/react/global/non-www/templates/got/functions.inc.php on line 303", "(warning@1 OR (Zstat@2 OR Zfail@3 OR Zfor@4) OR (mnt@5 PHRASE 12 web@6 PHRASE 12 react@7 PHRASE 12 got@8 PHRASE 12 react@9 PHRASE 12 board@10 PHRASE 12 non@11 PHRASE 12 www@12 PHRASE 12 headlines@13 PHRASE 12 tnet@14 PHRASE 12 headlines@15 PHRASE 12 txt@16) OR (errno@17 OR 2@18 OR (no@19 OR Zsuch@20 OR Zfile@21 OR Zor@22 OR Zdirectori@23)) OR Zin@24 OR (mnt@25 PHRASE 13 web@26 PHRASE 13 react@27 PHRASE 13 got@28 PHRASE 13 react@29 PHRASE 13 global@30 PHRASE 13 non@31 PHRASE 13 www@32 PHRASE 13 templates@33 PHRASE 13 got@34 PHRASE 13 functions@35 PHRASE 13 inc@36 PHRASE 13 php@37) OR (Zon@38 OR Zline@39 OR 303@40))" },
511 { "apm: BIOS version 1.2 Flags 0x03 (Driver version 1.16)", "(Zapm@1 OR (bios@2 OR Zversion@3 OR 1.2@4 OR flags@5 OR 0x03@6) OR (driver@7 OR Zversion@8 OR 1.16@9))" },
512 { "GA-8IHXP(3.0)", "((ga@1 PHRASE 2 8ihxp@2) OR 3.0@3)" },
513 { "8IHXP(3.0)", "(8ihxp@1 OR 3.0@2)" },
514 { "na\xc2\xb7si (de ~ (m.))", "(Zna\xc2\xb7si@1 OR (Zde@2 OR Zm@3))" },
515 { "header(\"Content-Disposition: attachment;", "(header@1 OR (content@2 PHRASE 3 disposition@3 PHRASE 3 attachment@4))" },
516 { "\"header(\"Content-Disposition: attachment;\"", "(header@1 OR (content@2 PHRASE 2 disposition@3) OR Zattach@4)" },
517 { "\"Beep -f\"", "(beep@1 PHRASE 2 f@2)" },
518 { "kraan NEAR (Elektrisch OR Electrisch)", "(Zkraan@1 OR near@2 OR (elektrisch@3 OR or@4 OR electrisch@5))" },
519 { "checking for Qt... configure: error: Qt (>= Qt 3.0.2) (headers and libraries) not found. Please check your installation!", "(Zcheck@1 OR Zfor@2 OR qt@3 OR Zconfigur@4 OR Zerror@5 OR qt@6 OR (qt@7 OR 3.0.2@8) OR (Zheader@9 OR Zand@10 OR Zlibrari@11) OR (Znot@12 OR Zfound@13 OR please@14 OR Zcheck@15 OR Zyour@16 OR Zinstal@17))" },
520 { "parse error, unexpected '\\\"', expecting T_STRING or T_VARIABLE or T_NUM_STRING", "(Zpars@1 OR Zerror@2 OR Zunexpect@3 OR (expecting@4 PHRASE 6 t_string@5 PHRASE 6 or@6 PHRASE 6 t_variable@7 PHRASE 6 or@8 PHRASE 6 t_num_string@9))" },
521 { "ac3 (0x2000) \"Dolby Laboratories,", "(Zac3@1 OR 0x2000@2 OR (dolby@3 PHRASE 2 laboratories@4))" },
522 { "Movie.FileName=(\"../../../~animations/\"+lesson1.recordset.fields('column3')+\"Intro.avi\")", "((movie@1 PHRASE 2 filename@2) OR animations@3 OR (lesson1@4 PHRASE 3 recordset@5 PHRASE 3 fields@6) OR Zcolumn3@7 OR (intro@8 PHRASE 2 avi@9))" },
523 { "502 Permission Denied - Permission Denied - news.chello.nl -- http://www.chello.nl/ (Typhoon v1.2.3)", "(502@1 OR permission@2 OR denied@3 OR (permission@4 OR denied@5) OR (news@6 PHRASE 3 chello@7 PHRASE 3 nl@8) OR (http@9 PHRASE 4 www@10 PHRASE 4 chello@11 PHRASE 4 nl@12) OR (typhoon@13 OR Zv1.2.3@14))" },
524 { "Motion JPEG (MJPEG codec)", "(motion@1 OR jpeg@2 OR (mjpeg@3 OR Zcodec@4))" },
525 { ": zoomtext\"", "zoomtext@1" },
526 { "Your SORT command does not seem to support the \"-r -n -k 7\"", "(your@1 OR sort@2 OR Zcommand@3 OR Zdoe@4 OR Znot@5 OR Zseem@6 OR Zto@7 OR Zsupport@8 OR Zthe@9 OR (r@10 PHRASE 4 n@11 PHRASE 4 k@12 PHRASE 4 7@13))" },
527 { "Geef de naam van de MSDOS prompt op C:\\\\WINDOWS.COM\\\"", "(geef@1 OR Zde@2 OR Znaam@3 OR Zvan@4 OR Zde@5 OR msdos@6 OR Zprompt@7 OR Zop@8 OR (c@9 PHRASE 3 windows@10 PHRASE 3 com@11))" },
528 { "\"\"wa is fase\"", "(Zwa@1 OR Zis@2 OR fase@3)" },
529 { "<v:imagedata src=\"", "((v@1 PHRASE 2 imagedata@2) OR src@3)" },
530 { "system(play ringin.wav); ?>", "(system@1 OR Zplay@2 OR (ringin@3 PHRASE 2 wav@4))" },
531 { "\"perfect NEAR systems\"", "(perfect@1 PHRASE 3 near@2 PHRASE 3 systems@3)" },
532 { "LoadLibrary(\"mainta/gamex86.dll\") failed", "(loadlibrary@1 OR (mainta@2 PHRASE 3 gamex86@3 PHRASE 3 dll@4) OR Zfail@5)" },
533 { "DATE_FORMAT('1997-10-04 22:23:00', '%W %M %Y');", "(date_format@1 OR (1997@2 PHRASE 3 10@3 PHRASE 3 04@4) OR (22@5 PHRASE 3 23@6 PHRASE 3 00@7) OR w@8 OR m@9 OR y@10)" },
534 { "secundaire IDE-controller (dubbele fifo)", "(Zsecundair@1 OR (ide@2 PHRASE 2 controller@3) OR (Zdubbel@4 OR Zfifo@5))" },
535 { "\"Postal2+Explorer.exe\"", "(postal2@1 PHRASE 3 explorer@2 PHRASE 3 exe@3)" },
536 { "COUNT(*)", "count@1" },
537 { "Nuttige Windows progs (1/11)", "(nuttige@1 OR windows@2 OR Zprog@3 OR (1@4 PHRASE 2 11@5))" },
538 { "if(usercode==passcode==)", "(if@1 OR usercode@2 OR passcode@3)" },
539 { "lg 8160b (dvd+r)", "(Zlg@1 OR 8160b@2 OR (Zdvd@3 OR Zr@4))" },
540 { "iPAQ Pocket PC 2002 End User Update (EUU - Service Pack)", "(Zipaq@1 OR pocket@2 OR pc@3 OR 2002@4 OR end@5 OR user@6 OR update@7 OR (euu@8 OR (service@9 OR pack@10)))" },
541 { "'ipod pakt tags niet\"", "(Zipod@1 OR Zpakt@2 OR Ztag@3 OR niet@4)" },
542 { "\"DVD+/-R\"", "(dvd+@1 PHRASE 2 r@2)" },
543 { "\"DVD+R DVD-R\"", "(dvd@1 PHRASE 4 r@2 PHRASE 4 dvd@3 PHRASE 4 r@4)" },
544 { "php ;) in een array zetten", "(Zphp@1 OR (Zin@2 OR Zeen@3 OR Zarray@4 OR Zzetten@5))" },
545 { "De inhoud van uw advertentie is niet geschikt voor plaatsing op marktplaats! (001", "(de@1 OR Zinhoud@2 OR Zvan@3 OR Zuw@4 OR Zadvertenti@5 OR Zis@6 OR Zniet@7 OR Zgeschikt@8 OR Zvoor@9 OR Zplaats@10 OR Zop@11 OR Zmarktplaat@12 OR 001@13)" },
546 { "creative (soundblaster OR sb) 128", "(Zcreativ@1 OR (Zsoundblast@2 OR Zsb@3) OR 128@4)" },
547 { "Can't open file: (errno: 145)", "(can't@1 OR Zopen@2 OR Zfile@3 OR (Zerrno@4 OR 145@5))" },
548 { "Formateren lukt niet(98,XP)", "(formateren@1 OR Zlukt@2 OR niet@3 OR 98@4 OR xp@5)" },
549 { "access denied (java.io.", "(Zaccess@1 OR Zdeni@2 OR (java@3 PHRASE 2 io@4))" },
550 { "(access denied (java.io.)", "(Zaccess@1 OR Zdeni@2 OR (java@3 PHRASE 2 io@4))" },
551 { "wil niet installeren ( crc fouten)", "(Zwil@1 OR Zniet@2 OR Zinstalleren@3 OR (Zcrc@4 OR Zfouten@5))" },
552 { "(DVD+RW) brandsoftware meerdere", "(dvd@1 OR rw@2 OR (Zbrandsoftwar@3 OR Zmeerder@4))" },
553 { "(database OF databases) EN geheugen", "(Zdatabas@1 OR of@2 OR Zdatabas@3 OR (en@4 OR Zgeheugen@5))" },
554 { "(server 2003) winroute", "(Zserver@1 OR 2003@2 OR Zwinrout@3)" },
555 { "54MHz (kanaal 2 VHF) tot tenminste 806 MHz (kanaal 69 UHF)", "(54mhz@1 OR (Zkanaal@2 OR 2@3 OR vhf@4) OR (Ztot@5 OR Ztenminst@6 OR 806@7 OR mhz@8) OR (Zkanaal@9 OR 69@10 OR uhf@11))" },
556 { "(draadloos OR wireless) netwerk", "(Zdraadloo@1 OR Zwireless@2 OR Znetwerk@3)" },
557 { "localtime(time(NULL));", "(localtime@1 OR time@2 OR null@3)" },
558 { "ob_start(\"ob_gzhandler\");", "(ob_start@1 OR ob_gzhandler@2)" },
559 { "PPP Closed : LCP Time-out (VPN-0)", "(ppp@1 OR closed@2 OR lcp@3 OR (time@4 PHRASE 2 out@5) OR (vpn@6 PHRASE 2 0@7))" },
560 { "COM+-gebeurtenissysteem", "(com+@1 PHRASE 2 gebeurtenissysteem@2)" },
561 { "rcpthosts (#5.7.1)", "(Zrcpthost@1 OR 5.7.1@2)" },
562 { "Dit apparaat werkt niet goed omdat Windows de voor dit apparaat vereiste stuurprogramma's niet kan laden. (Code 31)", "(dit@1 OR Zapparaat@2 OR Zwerkt@3 OR Zniet@4 OR Zgo@5 OR Zomdat@6 OR windows@7 OR Zde@8 OR Zvoor@9 OR Zdit@10 OR Zapparaat@11 OR Zvereist@12 OR Zstuurprogramma@13 OR Zniet@14 OR Zkan@15 OR Zladen@16 OR (code@17 OR 31@18))" },
563 { "window.open( scrollbar", "((window@1 PHRASE 2 open@2) OR Zscrollbar@3)" },
564 { "T68i truc ->", "(t68i@1 OR Ztruc@2)" },
565 { "T68i ->", "t68i@1" },
566 { "\"de lijn is bezet\"\"", "(de@1 PHRASE 4 lijn@2 PHRASE 4 is@3 PHRASE 4 bezet@4)" },
567 { "if (eregi(\"", "(Zif@1 OR eregi@2)" },
568 { "This device is not working properly because Windows cannot load the drivers required for this device. (Code 31)", "(this@1 OR Zdevic@2 OR Zis@3 OR Znot@4 OR Zwork@5 OR Zproper@6 OR Zbecaus@7 OR windows@8 OR Zcannot@9 OR Zload@10 OR Zthe@11 OR Zdriver@12 OR Zrequir@13 OR Zfor@14 OR Zthis@15 OR Zdevic@16 OR (code@17 OR 31@18))" },
569 { "execCommand(\"Paste\");", "(execcommand@1 OR paste@2)" },
570 { "\"-1 unread\"", "(1@1 PHRASE 2 unread@2)" },
571 { "\"www.historical-fire-engines", "(www@1 PHRASE 4 historical@2 PHRASE 4 fire@3 PHRASE 4 engines@4)" },
572 { "\"DVD+RW\" erase", "((dvd@1 PHRASE 2 rw@2) OR Zeras@3)" },
573 { "[showjekamer)", "Zshowjekam@1" },
574 { "The description for Event ID 1 in Source True Vector Engine ) cannot be found. The local computer may not have the necessary registry information or message DLL files to display messages from a remote computer. You may be able to use the /AUXSOURC", "(the@1 OR Zdescript@2 OR Zfor@3 OR event@4 OR id@5 OR 1@6 OR Zin@7 OR source@8 OR true@9 OR vector@10 OR engine@11 OR (Zcannot@12 OR Zbe@13 OR Zfound@14 OR the@15 OR Zlocal@16 OR Zcomput@17 OR Zmay@18 OR Znot@19 OR Zhave@20 OR Zthe@21 OR Znecessari@22 OR Zregistri@23 OR Zinform@24 OR Zor@25 OR Zmessag@26 OR dll@27 OR Zfile@28 OR Zto@29 OR Zdisplay@30 OR Zmessag@31 OR Zfrom@32 OR Za@33 OR Zremot@34 OR Zcomput@35 OR you@36 OR Zmay@37 OR Zbe@38 OR Zabl@39 OR Zto@40 OR Zuse@41 OR Zthe@42) OR auxsourc@43)" },
575 { "org.apache.jasper.JasperException: This absolute uri (http://java.sun.com/jstl/core) cannot be resolved in either web.xml or the jar files deployed with this application", "((org@1 PHRASE 4 apache@2 PHRASE 4 jasper@3 PHRASE 4 jasperexception@4) OR (this@5 OR Zabsolut@6 OR Zuri@7) OR (http@8 PHRASE 6 java@9 PHRASE 6 sun@10 PHRASE 6 com@11 PHRASE 6 jstl@12 PHRASE 6 core@13) OR (Zcannot@14 OR Zbe@15 OR Zresolv@16 OR Zin@17 OR Zeither@18) OR (web@19 PHRASE 2 xml@20) OR (Zor@21 OR Zthe@22 OR Zjar@23 OR Zfile@24 OR Zdeploy@25 OR Zwith@26 OR Zthis@27 OR Zapplic@28))" },
576 { "This absolute uri (http://java.sun.com/jstl/core) cannot be resolved in either web.xml or the jar files deployed with this application", "(this@1 OR Zabsolut@2 OR Zuri@3 OR (http@4 PHRASE 6 java@5 PHRASE 6 sun@6 PHRASE 6 com@7 PHRASE 6 jstl@8 PHRASE 6 core@9) OR (Zcannot@10 OR Zbe@11 OR Zresolv@12 OR Zin@13 OR Zeither@14) OR (web@15 PHRASE 2 xml@16) OR (Zor@17 OR Zthe@18 OR Zjar@19 OR Zfile@20 OR Zdeploy@21 OR Zwith@22 OR Zthis@23 OR Zapplic@24))" },
577 { "vervangen # \"/", "Zvervangen@1" },
578 { "vervangen # /\"", "Zvervangen@1" },
579 { "while(list($key, $val) = each($HTTP_POST_VARS))", "(while@1 OR list@2 OR Zkey@3 OR Zval@4 OR each@5 OR http_post_vars@6)" },
580 { "PowerDVD does not support the current display mode. (DDraw Overlay mode is recommended)", "(powerdvd@1 OR Zdoe@2 OR Znot@3 OR Zsupport@4 OR Zthe@5 OR Zcurrent@6 OR Zdisplay@7 OR Zmode@8 OR (ddraw@9 OR overlay@10 OR Zmode@11 OR Zis@12 OR Zrecommend@13))" },
581 { "Warning: Unexpected character in input: '' (ASCII=92) state=1 highlight", "(warning@1 OR (unexpected@2 OR Zcharact@3 OR Zin@4 OR Zinput@5) OR (ascii@6 OR 92@7) OR state@8 OR (1@9 OR Zhighlight@10))" },
582 { "error: Qt-1.4 (headers and libraries) not found. Please check your installation!", "(Zerror@1 OR (qt@2 PHRASE 2 1.4@3) OR (Zheader@4 OR Zand@5 OR Zlibrari@6) OR (Znot@7 OR Zfound@8 OR please@9 OR Zcheck@10 OR Zyour@11 OR Zinstal@12))" },
583 { "Error while initializing the sound driver: device /dev/dsp can't be opened (No such device) The sound server will continue, using the null output device.", "(error@1 OR Zwhile@2 OR Ziniti@3 OR Zthe@4 OR Zsound@5 OR Zdriver@6 OR Zdevic@7 OR (dev@8 PHRASE 2 dsp@9) OR (Zcan't@10 OR Zbe@11 OR Zopen@12) OR (no@13 OR Zsuch@14 OR Zdevic@15) OR (the@16 OR Zsound@17 OR Zserver@18 OR Zwill@19 OR Zcontinu@20) OR (Zuse@21 OR Zthe@22 OR Znull@23 OR Zoutput@24 OR Zdevic@25))" },
584 { "mag mijn waarschuwing nu weg ? ;)", "(Zmag@1 OR Zmijn@2 OR Zwaarschuw@3 OR Znu@4 OR Zweg@5)" },
585 { "Abit NF7-S (nForce 2 Chipset) Rev 2.0", "(abit@1 OR (nf7@2 PHRASE 2 s@3) OR (Znforc@4 OR 2@5 OR chipset@6) OR (rev@7 OR 2.0@8))" },
586 { "Setup Could Not Verify the Integrity of the File\" Error Message Occurs When You Try to Install Windows XP Service Pack 1", "(setup@1 OR could@2 OR not@3 OR verify@4 OR Zthe@5 OR integrity@6 OR Zof@7 OR Zthe@8 OR file@9 OR (error@10 PHRASE 13 message@11 PHRASE 13 occurs@12 PHRASE 13 when@13 PHRASE 13 you@14 PHRASE 13 try@15 PHRASE 13 to@16 PHRASE 13 install@17 PHRASE 13 windows@18 PHRASE 13 xp@19 PHRASE 13 service@20 PHRASE 13 pack@21 PHRASE 13 1@22))" },
587 { "(browser 19) citrix", "(Zbrowser@1 OR 19@2 OR Zcitrix@3)" },
588 { "preg_replace (.*?)", "Zpreg_replac@1" },
589 { "formule excel #naam\"?\"", "(Zformul@1 OR Zexcel@2 OR naam@3)" },
590 { "->", "" },
591 { "De instructie op 0x77f436f7 verwijst naar geheugen op 0x007f4778. De lees-of schrijfbewerking (\"written\") op het geheugen is mislukt", "(de@1 OR Zinstructi@2 OR Zop@3 OR 0x77f436f7@4 OR Zverwijst@5 OR Znaar@6 OR Zgeheugen@7 OR Zop@8 OR 0x007f4778@9 OR de@10 OR (lees@11 PHRASE 2 of@12) OR Zschrijfbewerk@13 OR written@14 OR (Zop@15 OR Zhet@16 OR Zgeheugen@17 OR Zis@18 OR Zmislukt@19))" },
592 { "<iframe src=\"www.tweakers.net></iframe>", "(Zifram@1 OR src@2 OR (www@3 PHRASE 4 tweakers@4 PHRASE 4 net@5 PHRASE 4 iframe@6))" },
593 { "\"rpm -e httpd\"", "(rpm@1 PHRASE 3 e@2 PHRASE 3 httpd@3)" },
594 { "automatisch op All Flis (*.*)", "(Zautomatisch@1 OR Zop@2 OR all@3 OR flis@4)" },
595 { "(Windows; U; Windows NT 5.1; en-US; rv:1.3b) Gecko/20030210", "(windows@1 OR u@2 OR (windows@3 OR nt@4 OR 5.1@5) OR (en@6 PHRASE 2 us@7) OR (rv@8 PHRASE 2 1.3b@9) OR (gecko@10 PHRASE 2 20030210@11))" },
596 { "en-US; rv:1.3b) Gecko/20030210", "((en@1 PHRASE 2 us@2) OR (rv@3 PHRASE 2 1.3b@4) OR (gecko@5 PHRASE 2 20030210@6))" },
597 { "\"en-US; rv:1.3b) Gecko/20030210\"", "(en@1 PHRASE 6 us@2 PHRASE 6 rv@3 PHRASE 6 1.3b@4 PHRASE 6 gecko@5 PHRASE 6 20030210@6)" },
598 { "(./) chmod.sh", "(chmod@1 PHRASE 2 sh@2)" },
599 { "document.write(ssg(\" html", "((document@1 PHRASE 2 write@2) OR ssg@3 OR html@4)" },
600 { "superstack \"mac+adressen\"", "(Zsuperstack@1 OR (mac@2 PHRASE 2 adressen@3))" },
601 { "IIS getenv(REMOTE_HOST)_", "(iis@1 OR getenv@2 OR remote_host@3 OR _@4)" },
602 { "IIS en getenv(REMOTE_HOST)", "(iis@1 OR Zen@2 OR getenv@3 OR remote_host@4)" },
603 { "php getenv(\"HTTP_REFERER\")", "(Zphp@1 OR getenv@2 OR http_referer@3)" },
604 { "nec+-1300", "(nec+@1 PHRASE 2 1300@2)" },
605 { "smbpasswd script \"-s\"", "(Zsmbpasswd@1 OR Zscript@2 OR s@3)" },
606 { "leestekens \" \xc3\xb6 \xc3\xab", "(Zleesteken@1 OR (\xc3\xb6@2 PHRASE 2 \xc3\xab@3))" },
607 { "freesco and (all seeing eye)", "(Zfreesco@1 OR Zand@2 OR (Zall@3 OR Zsee@4 OR Zeye@5))" },
608 { "('all seeing eye') and freesco", "(Zall@1 OR Zsee@2 OR Zeye@3 OR (Zand@4 OR Zfreesco@5))" },
609 { "\"[......\"", "" },
610 { "Error = 11004 (500 No Data (Winsock error #11004))", "(error@1 OR 11004@2 OR (500@3 OR no@4 OR data@5 OR (winsock@6 OR Zerror@7 OR 11004@8)))" },
611 { "gegevensfout (cyclishe redundantiecontrole)", "(Zgegevensfout@1 OR (Zcyclish@2 OR Zredundantiecontrol@3))" },
612 { "firmware versie waar NEC\"", "(Zfirmwar@1 OR Zversi@2 OR Zwaar@3 OR nec@4)" },
613 { "nu.nl \"-1\"", "((nu@1 PHRASE 2 nl@2) OR 1@3)" },
614 { "provider+-webspace", "(provider+@1 PHRASE 2 webspace@2)" },
615 { "verschil \"dvd+rw\" \"dvd-rw\"", "(Zverschil@1 OR (dvd@2 PHRASE 2 rw@3) OR (dvd@4 PHRASE 2 rw@5))" },
616 { "(dhcp client) + hangt", "(Zdhcp@1 OR Zclient@2 OR Zhangt@3)" },
617 { "MSI 875P Neo-FIS2R (Intel 875P)", "(msi@1 OR 875p@2 OR (neo@3 PHRASE 2 fis2r@4) OR (intel@5 OR 875p@6))" },
618 { "voeding passief gekoeld\"", "(Zvoed@1 OR Zpassief@2 OR gekoeld@3)" },
619 { "if (mysql_num_rows($resultaat)==1)", "(Zif@1 OR mysql_num_rows@2 OR Zresultaat@3 OR 1@4)" },
620 { "Server.CreateObject(\"Persits.Upload.1\")", "((server@1 PHRASE 2 createobject@2) OR (persits@3 PHRASE 3 upload@4 PHRASE 3 1@5))" },
621 { "if(cod>9999999)cod=parseInt(cod/64)", "(if@1 OR cod@2 OR 9999999@3 OR cod@4 OR parseint@5 OR (cod@6 PHRASE 2 64@7))" },
622 { "if (cod>9999999", "(Zif@1 OR (cod@2 OR 9999999@3))" },
623 { "\"rm -rf /bin/laden\"", "(rm@1 PHRASE 4 rf@2 PHRASE 4 bin@3 PHRASE 4 laden@4)" },
624 { "\">>> 0) & 0xFF\"", "(0@1 PHRASE 2 0xff@2)" },
625 { "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"> document.body.scrollHeight", "(doctype@1 OR html@2 OR public@3 OR (w3c@4 PHRASE 5 dtd@5 PHRASE 5 html@6 PHRASE 5 4.01@7 PHRASE 5 en@8) OR (document@9 PHRASE 3 body@10 PHRASE 3 scrollheight@11))" },
626 { "<BR>window.resizeBy(offsetX,offsetY)<P>kweet", "(br@1 OR (window@2 PHRASE 2 resizeby@3) OR Zoffsetx@4 OR Zoffseti@5 OR p@6 OR Zkweet@7)" },
627 { "linux humor :)", "(Zlinux@1 OR Zhumor@2)" },
628 { "ClassFactory kan aangevraagde klasse niet leveren (Fout=80040111)", "(classfactory@1 OR Zkan@2 OR Zaangevraagd@3 OR Zklass@4 OR Zniet@5 OR Zleveren@6 OR (fout@7 OR 80040111@8))" },
629 { "remote_smtp defer (-44)", "(Zremote_smtp@1 OR Zdefer@2 OR 44@3)" },
630 { "txtlogin.getText().trim().toUpperCase().intern() == inuser[2 * (i - 1) + 2].trim().toUpperCase().intern() && txtpass.getText().trim().toUpperCase().intern() == inuser[2 * (i - 1) + 3].trim().toUpperCase().intern())", "((txtlogin@1 PHRASE 2 gettext@2) OR trim@3 OR touppercase@4 OR intern@5 OR inuser@6 OR 2@7 OR Zi@8 OR 1@9 OR 2@10 OR trim@11 OR touppercase@12 OR intern@13 OR (txtpass@14 PHRASE 2 gettext@15) OR trim@16 OR touppercase@17 OR intern@18 OR inuser@19 OR 2@20 OR Zi@21 OR 1@22 OR 3@23 OR trim@24 OR touppercase@25 OR intern@26)" },
631 { "Koper + amoniak (NH2", "(koper@1 OR Zamoniak@2 OR nh2@3)" },
632 { "nec dvd -/+r", "((Znec@1 OR Zdvd@2) AND_NOT Zr@3)" },
633 { "er is een gereserveerde fout (-1104) opgetreden", "(Zer@1 OR Zis@2 OR Zeen@3 OR Zgereserveerd@4 OR Zfout@5 OR 1104@6 OR Zopgetreden@7)" },
634 { "Cor \\(CCN\\)'\" <cor.kloet@ccn.controlec.nl>", "(cor@1 OR ccn@2 OR (cor@3 PHRASE 5 kloet@4 PHRASE 5 ccn@5 PHRASE 5 controlec@6 PHRASE 5 nl@7))" },
635 { "Warning: Failed opening for inclusion (include_path='') in Unknown on line 0", "(warning@1 OR (failed@2 OR Zopen@3 OR Zfor@4 OR Zinclus@5) OR include_path@6 OR (Zin@7 OR unknown@8 OR Zon@9 OR Zline@10 OR 0@11))" },
636 { "\"~\" + \"c:\\\"", "Zc@1" },
637 { "mysql count(*)", "(Zmysql@1 OR count@2)" },
638 { "for %f in (*.*) do", "(Zfor@1 OR (Zf@2 OR Zin@3) OR Zdo@4)" },
639 { "raar \"~\" bestand", "(Zraar@1 OR Zbestand@2)" },
640 { "NEC DVD +-R/RW 1300", "(nec@1 OR dvd@2 OR (r@3 PHRASE 2 rw@4) OR 1300@5)" },
641 { "approved (ref: 38446-263)", "(Zapprov@1 OR (Zref@2 OR (38446@3 PHRASE 2 263@4)))" },
642 { "GA-7VRXP(2.0)", "((ga@1 PHRASE 2 7vrxp@2) OR 2.0@3)" },
643 { "~ Could not retrieve directory listing for \"/\"", "(could@1 OR Znot@2 OR Zretriev@3 OR Zdirectori@4 OR Zlist@5 OR Zfor@6)" },
644 { "asp CreateObject(\"Word.Document\")", "(Zasp@1 OR createobject@2 OR (word@3 PHRASE 2 document@4))" },
645 { "De lees- of schrijfbewerking (\"written\") op het geheugen is mislukt.", "(de@1 OR Zlee@2 OR Zof@3 OR Zschrijfbewerk@4 OR written@5 OR (Zop@6 OR Zhet@7 OR Zgeheugen@8 OR Zis@9 OR Zmislukt@10))" },
646 { "putStr (map (\\x -> chr (round (21/2 * x^3 - 92 * x^2 + 503/2 * x - 105))) [1..4])", "(Zputstr@1 OR (Zmap@2 OR ((Zx@3 OR (Zround@5 OR ((21@6 PHRASE 2 2@7) OR Zx@8 OR 3@9 OR 92@10 OR Zx@11 OR 2@12 OR (503@13 PHRASE 2 2@14) OR Zx@15 OR 105@16))) AND_NOT Zchr@4) OR (1@17 PHRASE 2 4@18)))" },
647 { "parent.document.getElementById(\\\"leftmenu\\\").cols", "((parent@1 PHRASE 3 document@2 PHRASE 3 getelementbyid@3) OR leftmenu@4 OR Zcol@5)" },
648 { "<% if not isEmpty(Request.QueryString) then", "(Zif@1 OR Znot@2 OR isempty@3 OR (request@4 PHRASE 2 querystring@5) OR Zthen@6)" },
649 { "Active Desktop (Hier issie)", "(active@1 OR desktop@2 OR (hier@3 OR Zissi@4))" },
650 { "Asus A7V8X (LAN + Sound)", "(asus@1 OR a7v8x@2 OR (lan@3 OR sound@4))" },
651 { "Novell This pentium class machine (or greater) lacks some required CPU feature(s", "(novell@1 OR this@2 OR Zpentium@3 OR Zclass@4 OR Zmachin@5 OR (Zor@6 OR Zgreater@7) OR (Zlack@8 OR Zsome@9 OR Zrequir@10 OR cpu@11 OR feature@12) OR Zs@13)" },
652 { "sql server install fails error code (-1)", "(Zsql@1 OR Zserver@2 OR Zinstal@3 OR Zfail@4 OR Zerror@5 OR Zcode@6 OR 1@7)" },
653 { "session_register(\"login\");", "(session_register@1 OR login@2)" },
654 { "\"kylix+ndmb\"", "(kylix@1 PHRASE 2 ndmb@2)" },
655 { "Cannot find imap library (libc-client.a).", "(cannot@1 OR Zfind@2 OR Zimap@3 OR Zlibrari@4 OR (libc@5 PHRASE 3 client@6 PHRASE 3 a@7))" },
656 { "If ($_SESSION[\"Login\"] == 1)", "(if@1 OR (_session@2 OR login@3 OR 1@4))" },
657 { "You have an error in your SQL syntax near '1')' at line 1", "(you@1 OR Zhave@2 OR Zan@3 OR Zerror@4 OR Zin@5 OR Zyour@6 OR sql@7 OR Zsyntax@8 OR Znear@9 OR 1@10 OR (Zat@11 OR Zline@12 OR 1@13))" },
658 { "ASRock K7VT2 (incl. LAN)", "(asrock@1 OR k7vt2@2 OR (Zincl@3 OR lan@4))" },
659 { "+windows98 +(geen communicatie) +ie5", "(Zwindows98@1 AND (Zgeen@2 OR Zcommunicati@3) AND Zie5@4)" },
660 { "\"xterm -fn\"", "(xterm@1 PHRASE 2 fn@2)" },
661 { "IRQL_NOT_LESS_OR_EQUAL", "irql_not_less_or_equal@1" },
662 { "access query \"NOT IN\"", "(Zaccess@1 OR Zqueri@2 OR (not@3 PHRASE 2 in@4))" },
663 { "\"phrase one \"phrase two\"", "((phrase@1 PHRASE 2 one@2) OR (Zphrase@3 OR two@4))" },
664 { "NEAR 207 46 249 27", "(near@1 OR 207@2 OR 46@3 OR 249@4 OR 27@5)" },
665 { "- NEAR 12V voeding", "(near@1 OR 12v@2 OR Zvoed@3)" },
666 { "waarom \"~\" in directorynaam", "(Zwaarom@1 OR (Zin@2 OR Zdirectorynaam@3))" },
667 { "cd'r NEAR toebehoren", "(cd'r@1 NEAR 11 toebehoren@2)" },
668 { "site:1 site:2", "0 * (H1 OR H2)" },
669 { "site:1 site2:2", "0 * (H1 AND J2)" },
670 { "site:1 site:2 site2:2", "0 * ((H1 OR H2) AND J2)" },
671 { "site:1 OR site:2", "(0 * H1 OR 0 * H2)" },
672 { "site:1 AND site:2", "(0 * H1 AND 0 * H2)" },
673 { "foo AND site:2", "(Zfoo@1 AND 0 * H2)" },
674 // Non-exclusive boolean prefixes feature tests (ticket#402):
675 { "category:1 category:2", "0 * (XCAT1 AND XCAT2)" },
676 { "category:1 site2:2", "0 * (XCAT1 AND J2)" },
677 { "category:1 category:2 site2:2", "0 * ((XCAT1 AND XCAT2) AND J2)" },
678 { "category:1 OR category:2", "(0 * XCAT1 OR 0 * XCAT2)" },
679 { "category:1 AND category:2", "(0 * XCAT1 AND 0 * XCAT2)" },
680 { "foo AND category:2", "(Zfoo@1 AND 0 * XCAT2)" },
681 // Regression test for combining multiple non-exclusive prefixes, fixed in
682 // 1.2.22 and 1.3.4.
683 { "category:1 dogegory:2", "0 * (XCAT1 AND XDOG2)" },
684 { "A site:1 site:2", "(a@1 FILTER (H1 OR H2))" },
685 #if 0
686 { "A (site:1 OR site:2)", "(a@1 FILTER (H1 OR H2))" },
687 #endif
688 { "A site:1 site2:2", "(a@1 FILTER (H1 AND J2))" },
689 { "A site:1 site:2 site2:2", "(a@1 FILTER ((H1 OR H2) AND J2))" },
690 #if 0
691 { "A site:1 OR site:2", "(a@1 FILTER (H1 OR H2))" },
692 #endif
693 { "A site:1 AND site:2", "((a@1 FILTER H1) AND 0 * H2)" },
694 { "site:xapian.org OR site:www.xapian.org", "(0 * Hxapian.org OR 0 * Hwww.xapian.org)" },
695 { "site:xapian.org site:www.xapian.org", "0 * (Hxapian.org OR Hwww.xapian.org)" },
696 { "site:xapian.org AND site:www.xapian.org", "(0 * Hxapian.org AND 0 * Hwww.xapian.org)" },
697 { "Xapian site:xapian.org site:www.xapian.org", "(xapian@1 FILTER (Hxapian.org OR Hwww.xapian.org))" },
698 { "author:richard author:olly writer:charlie", "(ZArichard@1 OR ZAolli@2 OR ZAcharli@3)"},
699 { "author:richard NEAR title:book", "(Arichard@1 NEAR 11 XTbook@2)"},
700 { "authortitle:richard NEAR title:book", "((Arichard@1 OR XTrichard@1) NEAR 11 XTbook@2)" },
701 { "multisite:xapian.org", "0 * (Hxapian.org OR Jxapian.org)"},
702 { "authortitle:richard", "(ZArichard@1 OR ZXTrichard@1)"},
703 { "multisite:xapian.org site:www.xapian.org author:richard authortitle:richard", "((ZArichard@1 OR (ZArichard@2 OR ZXTrichard@2)) FILTER ((Hxapian.org OR Jxapian.org) AND Hwww.xapian.org))" },
704 { "authortitle:richard-boulton", "((Arichard@1 PHRASE 2 Aboulton@2) OR (XTrichard@1 PHRASE 2 XTboulton@2))"},
705 { "authortitle:\"richard boulton\"", "((Arichard@1 PHRASE 2 Aboulton@2) OR (XTrichard@1 PHRASE 2 XTboulton@2))"},
706 // Test FLAG_NGRAMS isn't on by default:
707 { "久有归天愿", "Z久有归天愿@1" },
708 { NULL, "NGRAMS" }, // Enable FLAG_NGRAMS
709 // Test queries which don't need word break finding still parse the same:
710 { "gtk+ -gnome", "(Zgtk+@1 AND_NOT Zgnome@2)" },
711 { "“curly quotes”", "(curly@1 PHRASE 2 quotes@2)" },
712 // Test n-gram generation:
713 { "久有归天愿", "(久@1 AND 久有@1 AND 有@1 AND 有归@1 AND 归@1 AND 归天@1 AND 天@1 AND 天愿@1 AND 愿@1)" },
714 { "久有 归天愿", "((久@1 AND 久有@1 AND 有@1) OR (归@2 AND 归天@2 AND 天@2 AND 天愿@2 AND 愿@2))" },
715 { "久有!归天愿", "((久@1 AND 久有@1 AND 有@1) OR (归@2 AND 归天@2 AND 天@2 AND 天愿@2 AND 愿@2))" },
716 { "title:久有 归 天愿", "((XT久@1 AND XT久有@1 AND XT有@1) OR 归@2 OR (天@3 AND 天愿@3 AND 愿@3))" },
717 { "h众ello万众", "(Zh@1 OR 众@2 OR Zello@3 OR (万@4 AND 万众@4 AND 众@4))" },
718 { "世(の中)TEST_tm", "(世@1 OR (の@2 AND の中@2 AND 中@2) OR test_tm@3)" },
719 { "다녀 AND 와야", "(다@1 AND 다녀@1 AND 녀@1 AND (와@2 AND 와야@2 AND 야@2))" },
720 { "authortitle:학술 OR 연구를", "((A학@1 AND A학술@1 AND A술@1) OR (XT학@1 AND XT학술@1 AND XT술@1) OR (연@2 AND 연구@2 AND 구@2 AND 구를@2 AND 를@2))" },
721 // FIXME: These should really filter by bigrams to accelerate:
722 { "\"久有归\"", "(久@1 PHRASE 3 有@1 PHRASE 3 归@1)" },
723 { "\"久有test归\"", "(久@1 PHRASE 4 有@1 PHRASE 4 test@2 PHRASE 4 归@3)" },
724 // FIXME: this should work: { "久 NEAR 有", "(久@1 NEAR 11 有@2)" },
726 // Test Lao (added in 1.5.0).
727 { "\"ພາສາລາວ\"", "(ພ@1 PHRASE 7 າ@1 PHRASE 7 ສ@1 PHRASE 7 າ@1 PHRASE 7 ລ@1 PHRASE 7 າ@1 PHRASE 7 ວ@1)" },
729 // Test Myanmar (Burmese) (added in 1.5.0).
730 { "\"မြန်မာစကား\"", "(မ@1 PHRASE 10 ြ@1 PHRASE 10 န@1 PHRASE 10 ်@1 PHRASE 10 မ@1 PHRASE 10 ာ@1 PHRASE 10 စ@1 PHRASE 10 က@1 PHRASE 10 ာ@1 PHRASE 10 း@1)" },
732 // Test Khmer (added in 1.5.0).
733 { "\"ថ្លៃណាស់ \"", "(ថ@1 PHRASE 8 ្@1 PHRASE 8 ល@1 PHRASE 8 ៃ@1 PHRASE 8 ណ@1 PHRASE 8 ា@1 PHRASE 8 ស@1 PHRASE 8 ់@1)" },
735 { NULL, "WORD_BREAKS" }, // Enable FLAG_WORD_BREAKS
736 // Test word break finding
737 { "久有归天愿", "(久@1 AND 有@1 AND 归天@1 AND 愿@1)" },
738 { "久有 归天愿", "((久@1 AND 有@1) OR (归天@2 AND 愿@2))" },
739 { "久有!归天愿", "((久@1 AND 有@1) OR (归天@2 AND 愿@2))" },
741 { "title:久有 归 天愿", "((XT久@1 AND XT有@1) OR 归@2 OR (天@3 AND 愿@3))" },
743 { "h众ello万众", "(Zh@1 OR 众@2 OR Zello@3 OR (万@4 AND 众@4))" },
745 // Korean splits some words by whitespace, and there is no available tool
746 // to crosscheck Korean word splits for these tests. So the expected values
747 // here are best guess only.
748 { "世(の中)TEST_tm", "(世@1 OR (の@2 AND 中@2) OR test_tm@3)" },
749 { "다녀 AND 와야", "(다녀@1 AND 와야@2)" },
750 { "authortitle:학술 OR 연구를", "((A학술@1 AND XT학술@1) OR 연구를@2)" },
752 // Test Lao (added in 1.5.0).
753 { "\"ພາສາລາວ\"", "(ພາສາ@1 PHRASE 2 ລາວ@1)" },
755 // Test Myanmar (Burmese) (added in 1.5.0).
756 { "\"မြန်မာစကား\"", "(မြန်မာ@1 PHRASE 2 စကား@1)" },
758 // Test Khmer (added in 1.5.0).
759 { "\"សៀវភៅនេះថ្លៃណាស់ \"", "(សៀវភៅ@1 PHRASE 4 នេះ@1 PHRASE 4 ថ្លៃ@1 PHRASE 4 ណាស់@1)" },
761 // Test fullwidth Latin
762 { "\"hello ,world!\"", "(hello@1 PHRASE 2 world@2)" },
763 { "UFJ", "ufj@1" },
764 { "\"三菱UFJファクター\"", "(三菱@1 PHRASE 3 ufj@2 PHRASE 3 ファクター@3)" },
766 { "\"久有归天愿\"", "(久@1 PHRASE 4 有@1 PHRASE 4 归天@1 PHRASE 4 愿@1)" },
767 { "\"久有test归天\"", "(久@1 PHRASE 4 有@1 PHRASE 4 test@2 PHRASE 4 归天@3)" },
768 { "\"归天\"", "归天@1" },
769 // FIXME: this should work: { "久 NEAR 有", "(久@1 NEAR 11 有@2)" },
771 { NULL, NULL }
774 DEFINE_TESTCASE(queryparser1, !backend) {
775 Xapian::QueryParser queryparser;
776 queryparser.set_stemmer(Xapian::Stem("english"));
777 queryparser.set_stemming_strategy(Xapian::QueryParser::STEM_SOME);
778 queryparser.add_prefix("author", "A");
779 queryparser.add_prefix("writer", "A");
780 // Check that a redundant add_prefix() doesn't result in redundant terms in
781 // the Query object.
782 queryparser.add_prefix("author", "A");
783 queryparser.add_prefix("title", "XT");
784 queryparser.add_prefix("subject", "XT");
785 queryparser.add_prefix("authortitle", "A");
786 queryparser.add_prefix("authortitle", "XT");
787 queryparser.add_boolean_prefix("site", "H");
788 queryparser.add_boolean_prefix("site2", "J");
789 queryparser.add_boolean_prefix("multisite", "H");
790 queryparser.add_boolean_prefix("multisite", "J");
791 // Check that a redundant add_boolean_prefix() doesn't result in redundant
792 // terms in the Query object.
793 queryparser.add_boolean_prefix("multisite", "J");
794 queryparser.add_boolean_prefix("category", "XCAT", false);
795 queryparser.add_boolean_prefix("dogegory", "XDOG", false);
796 TEST_EXCEPTION(Xapian::InvalidOperationError,
797 queryparser.add_boolean_prefix("authortitle", "B");
799 TEST_EXCEPTION(Xapian::InvalidOperationError,
800 queryparser.add_prefix("multisite", "B");
802 unsigned flags = queryparser.FLAG_DEFAULT;
803 for (const test *p = test_or_queries; ; ++p) {
804 if (!p->query) {
805 if (!p->expect) break;
806 if (strcmp(p->expect, "NGRAMS") == 0) {
807 flags = queryparser.FLAG_DEFAULT|queryparser.FLAG_NGRAMS;
808 continue;
810 if (strcmp(p->expect, "WORD_BREAKS") == 0) {
811 flags = queryparser.FLAG_DEFAULT|queryparser.FLAG_WORD_BREAKS;
812 continue;
814 FAIL_TEST("Unknown flag code: " << p->expect);
816 string expect, parsed;
817 if (p->expect)
818 expect = p->expect;
819 else
820 expect = "parse error";
821 try {
822 Xapian::Query qobj = queryparser.parse_query(p->query, flags);
823 parsed = qobj.get_description();
824 expect = string("Query(") + expect + ')';
825 } catch (const Xapian::QueryParserError &e) {
826 parsed = e.get_msg();
827 } catch (const Xapian::Error &e) {
828 parsed = e.get_description();
829 } catch (...) {
830 parsed = "Unknown exception!";
832 #ifndef USE_ICU
833 if (flags & queryparser.FLAG_WORD_BREAKS) {
834 expect = "FeatureUnavailableError: FLAG_WORD_BREAKS requires "
835 "building Xapian to use ICU";
837 #endif
838 tout << "Query: " << p->query << '\n';
839 TEST_STRINGS_EQUAL(parsed, expect);
843 static const test test_and_queries[] = {
844 { "internet explorer title:(http www)", "(Zinternet@1 AND Zexplor@2 AND (ZXThttp@3 AND ZXTwww@4))" },
845 // Regression test for bug in 0.9.2 and earlier - this would give
846 // (two@2 AND_MAYBE (one@1 AND three@3))
847 { "one +two three", "(Zone@1 AND Ztwo@2 AND Zthree@3)" },
848 { "hello -title:\"hello world\"", "(Zhello@1 AND_NOT (XThello@2 PHRASE 2 XTworld@3))" },
849 // Regression test for bug fixed in 1.0.4 - the '-' would be ignored there
850 // because the whitespace after the '"' wasn't noticed.
851 { "\"hello world\" -C++", "((hello@1 PHRASE 2 world@2) AND_NOT c++@3)" },
852 // Regression tests for bug fixed in 1.0.4 - queries with only boolean
853 // filter and HATE terms weren't accepted.
854 { "-cup site:world", "(0 * Hworld AND_NOT Zcup@1)" },
855 { "site:world -cup", "(0 * Hworld AND_NOT Zcup@1)" },
856 // Regression test for bug fixed in 1.0.4 - the KET token for ')' was lost.
857 { "(site:world) -cup", "(0 * Hworld AND_NOT Zcup@1)" },
858 // Regression test for bug fixed in 1.0.4 - a boolean filter term between
859 // probabilistic terms caused a parse error (probably broken during the
860 // addition of synonym support in 1.0.2).
861 { "foo site:xapian.org bar", "((Zfoo@1 AND Zbar@2) FILTER Hxapian.org)" },
862 // Add coverage for other cases similar to the above.
863 { "a b site:xapian.org", "((Za@1 AND Zb@2) FILTER Hxapian.org)" },
864 { "site:xapian.org a b", "((Za@1 AND Zb@2) FILTER Hxapian.org)" },
865 { NULL, "NGRAMS" }, // Enable FLAG_NGRAMS
866 // Test n-gram generation:
867 { "author:험가 OR subject:万众 hello world!", "((A험@1 AND A험가@1 AND A가@1) OR (XT万@2 AND XT万众@2 AND XT众@2 AND (Zhello@3 AND Zworld@4)))" },
868 { "洛伊one儿差点two脸three", "(洛@1 AND 洛伊@1 AND 伊@1 AND Zone@2 AND (儿@3 AND 儿差@3 AND 差@3 AND 差点@3 AND 点@3) AND Ztwo@4 AND 脸@5 AND Zthree@6)" },
869 { NULL, "WORD_BREAKS" }, // Enable FLAG_WORD_BREAKS
870 // Test word break finding:
871 { "author:험가 OR subject:万众 hello world!", "(A험가@1 OR (XT万@2 AND XT众@2 AND (Zhello@3 AND Zworld@4)))" },
872 { "洛伊one儿差点two脸three", "(洛伊@1 AND Zone@2 AND (儿@3 AND 差点@3) AND Ztwo@4 AND 脸@5 AND Zthree@6)" },
873 { NULL, NULL }
876 // With default_op = OP_AND.
877 DEFINE_TESTCASE(qp_default_op1, !backend) {
878 Xapian::QueryParser queryparser;
879 queryparser.set_stemmer(Xapian::Stem("english"));
880 queryparser.set_stemming_strategy(Xapian::QueryParser::STEM_SOME);
881 queryparser.add_prefix("author", "A");
882 queryparser.add_prefix("title", "XT");
883 queryparser.add_prefix("subject", "XT");
884 queryparser.add_boolean_prefix("site", "H");
885 queryparser.set_default_op(Xapian::Query::OP_AND);
886 unsigned flags = queryparser.FLAG_DEFAULT;
887 for (const test *p = test_and_queries; ; ++p) {
888 if (!p->query) {
889 if (!p->expect) break;
890 if (strcmp(p->expect, "NGRAMS") == 0) {
891 flags = queryparser.FLAG_DEFAULT|queryparser.FLAG_NGRAMS;
892 continue;
894 if (strcmp(p->expect, "WORD_BREAKS") == 0) {
895 flags = queryparser.FLAG_DEFAULT|queryparser.FLAG_WORD_BREAKS;
896 continue;
898 FAIL_TEST("Unknown flag code: " << p->expect);
900 string expect, parsed;
901 if (p->expect)
902 expect = p->expect;
903 else
904 expect = "parse error";
905 try {
906 Xapian::Query qobj = queryparser.parse_query(p->query, flags);
907 parsed = qobj.get_description();
908 expect = string("Query(") + expect + ')';
909 } catch (const Xapian::QueryParserError &e) {
910 parsed = e.get_msg();
911 } catch (const Xapian::Error &e) {
912 parsed = e.get_description();
913 } catch (...) {
914 parsed = "Unknown exception!";
916 #ifndef USE_ICU
917 if (flags & queryparser.FLAG_WORD_BREAKS) {
918 expect = "FeatureUnavailableError: FLAG_WORD_BREAKS requires "
919 "building Xapian to use ICU";
921 #endif
922 tout << "Query: " << p->query << '\n';
923 TEST_STRINGS_EQUAL(parsed, expect);
927 // Feature test for specify the default prefix (new in Xapian 1.0.0).
928 DEFINE_TESTCASE(qp_default_prefix1, !backend) {
929 Xapian::QueryParser qp;
930 qp.set_stemmer(Xapian::Stem("english"));
931 qp.set_stemming_strategy(Xapian::QueryParser::STEM_SOME);
932 qp.add_prefix("title", "XT");
934 Xapian::Query qobj;
935 qobj = qp.parse_query("hello world", 0, "A");
936 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((ZAhello@1 OR ZAworld@2))");
937 qobj = qp.parse_query("me title:stuff", 0, "A");
938 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((ZAme@1 OR ZXTstuff@2))");
939 qobj = qp.parse_query("title:(stuff) me", Xapian::QueryParser::FLAG_BOOLEAN, "A");
940 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((ZXTstuff@1 OR ZAme@2))");
941 qobj = qp.parse_query("英国 title:文森hello", qp.FLAG_NGRAMS, "A");
942 TEST_STRINGS_EQUAL(qobj.get_description(), "Query(((A英@1 AND A英国@1 AND A国@1) OR (XT文@2 AND XT文森@2 AND XT森@2) OR ZAhello@3))");
945 // Feature test for setting the default prefix with add_prefix()
946 // (new in Xapian 1.0.3).
947 DEFINE_TESTCASE(qp_default_prefix2, !backend) {
948 Xapian::QueryParser qp;
949 qp.set_stemmer(Xapian::Stem("english"));
950 qp.set_stemming_strategy(Xapian::QueryParser::STEM_SOME);
952 // test that default prefixes can only be set with add_prefix().
953 TEST_EXCEPTION(Xapian::UnimplementedError,
954 qp.add_boolean_prefix("", "B");
957 qp.add_prefix("title", "XT");
958 qp.add_prefix("", "A");
960 Xapian::Query qobj;
961 qobj = qp.parse_query("hello world", 0);
962 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((ZAhello@1 OR ZAworld@2))");
963 qobj = qp.parse_query("me title:stuff", 0);
964 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((ZAme@1 OR ZXTstuff@2))");
965 qobj = qp.parse_query("title:(stuff) me", Xapian::QueryParser::FLAG_BOOLEAN);
966 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((ZXTstuff@1 OR ZAme@2))");
968 qobj = qp.parse_query("hello world", 0, "B");
969 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((ZBhello@1 OR ZBworld@2))");
970 qobj = qp.parse_query("me title:stuff", 0, "B");
971 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((ZBme@1 OR ZXTstuff@2))");
972 qobj = qp.parse_query("title:(stuff) me", Xapian::QueryParser::FLAG_BOOLEAN, "B");
973 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((ZXTstuff@1 OR ZBme@2))");
975 qp.add_prefix("", "B");
976 qobj = qp.parse_query("me-us title:(stuff) me", Xapian::QueryParser::FLAG_BOOLEAN);
977 TEST_STRINGS_EQUAL(qobj.get_description(), "Query(((Ame@1 PHRASE 2 Aus@2) OR (Bme@1 PHRASE 2 Bus@2) OR ZXTstuff@3 OR (ZAme@4 OR ZBme@4)))");
978 qobj = qp.parse_query("me-us title:(stuff) me", Xapian::QueryParser::FLAG_BOOLEAN, "C");
979 TEST_STRINGS_EQUAL(qobj.get_description(), "Query(((Cme@1 PHRASE 2 Cus@2) OR ZXTstuff@3 OR ZCme@4))");
981 qobj = qp.parse_query("me-us title:\"not-me\"", Xapian::QueryParser::FLAG_PHRASE);
982 TEST_STRINGS_EQUAL(qobj.get_description(), "Query(((Ame@1 PHRASE 2 Aus@2) OR (Bme@1 PHRASE 2 Bus@2) OR (XTnot@3 PHRASE 2 XTme@4)))");
985 // Test query with odd characters in.
986 DEFINE_TESTCASE(qp_odd_chars1, !backend) {
987 Xapian::QueryParser qp;
988 string query("\x01weird\x00stuff\x7f", 13);
989 Xapian::Query qobj = qp.parse_query(query);
990 tout << "Query: " << query << '\n';
991 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((weird@1 OR stuff@2))"); // FIXME: should these be stemmed?
994 // Test right truncation.
995 DEFINE_TESTCASE(qp_flag_wildcard1, backend) {
996 Xapian::Database db = get_database("qp_flag_wildcard1",
997 [](Xapian::WritableDatabase& wdb,
998 const string&) {
999 Xapian::Document doc;
1000 doc.add_term("abc");
1001 doc.add_term("main");
1002 doc.add_term("muscat");
1003 doc.add_term("muscle");
1004 doc.add_term("musclebound");
1005 doc.add_term("muscular");
1006 doc.add_term("mutton");
1007 wdb.add_document(doc);
1009 Xapian::QueryParser qp;
1010 qp.set_database(db);
1011 Xapian::Query qobj = qp.parse_query("ab*", Xapian::QueryParser::FLAG_WILDCARD);
1012 TEST_STRINGS_EQUAL(qobj.get_description(), "Query(WILDCARD SYNONYM ab)");
1013 qobj = qp.parse_query("muscle*", Xapian::QueryParser::FLAG_WILDCARD);
1014 TEST_STRINGS_EQUAL(qobj.get_description(), "Query(WILDCARD SYNONYM muscle)");
1015 qobj = qp.parse_query("meat*", Xapian::QueryParser::FLAG_WILDCARD);
1016 TEST_STRINGS_EQUAL(qobj.get_description(), "Query(WILDCARD SYNONYM meat)");
1017 qobj = qp.parse_query("musc*", Xapian::QueryParser::FLAG_WILDCARD);
1018 TEST_STRINGS_EQUAL(qobj.get_description(), "Query(WILDCARD SYNONYM musc)");
1019 qobj = qp.parse_query("mutt*", Xapian::QueryParser::FLAG_WILDCARD);
1020 TEST_STRINGS_EQUAL(qobj.get_description(), "Query(WILDCARD SYNONYM mutt)");
1021 // Regression test (we weren't lowercasing terms before checking if they
1022 // were in the database or not):
1023 qobj = qp.parse_query("mUTTON++");
1024 TEST_STRINGS_EQUAL(qobj.get_description(), "Query(mutton@1)");
1025 // Regression test: check that wildcards work with +terms.
1026 unsigned flags = Xapian::QueryParser::FLAG_WILDCARD |
1027 Xapian::QueryParser::FLAG_LOVEHATE;
1028 qobj = qp.parse_query("+mai* main", flags);
1029 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM mai AND_MAYBE main@2))");
1030 // Regression test (if we had a +term which was a wildcard and wasn't
1031 // present, the query could still match documents).
1032 qobj = qp.parse_query("foo* main", flags);
1033 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM foo OR main@2))");
1034 qobj = qp.parse_query("main foo*", flags);
1035 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@1 OR WILDCARD SYNONYM foo))");
1036 qobj = qp.parse_query("+foo* main", flags);
1037 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM foo AND_MAYBE main@2))");
1038 qobj = qp.parse_query("main +foo*", flags);
1039 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM foo AND_MAYBE main@1))");
1040 qobj = qp.parse_query("foo* +main", flags);
1041 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@2 AND_MAYBE WILDCARD SYNONYM foo))");
1042 qobj = qp.parse_query("+main foo*", flags);
1043 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@1 AND_MAYBE WILDCARD SYNONYM foo))");
1044 qobj = qp.parse_query("+foo* +main", flags);
1045 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM foo AND main@2))");
1046 qobj = qp.parse_query("+main +foo*", flags);
1047 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@1 AND WILDCARD SYNONYM foo))");
1048 qobj = qp.parse_query("foo* mai", flags);
1049 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM foo OR mai@2))");
1050 qobj = qp.parse_query("mai foo*", flags);
1051 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((mai@1 OR WILDCARD SYNONYM foo))");
1052 qobj = qp.parse_query("+foo* mai", flags);
1053 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM foo AND_MAYBE mai@2))");
1054 qobj = qp.parse_query("mai +foo*", flags);
1055 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM foo AND_MAYBE mai@1))");
1056 qobj = qp.parse_query("foo* +mai", flags);
1057 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((mai@2 AND_MAYBE WILDCARD SYNONYM foo))");
1058 qobj = qp.parse_query("+mai foo*", flags);
1059 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((mai@1 AND_MAYBE WILDCARD SYNONYM foo))");
1060 qobj = qp.parse_query("+foo* +mai", flags);
1061 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM foo AND mai@2))");
1062 qobj = qp.parse_query("+mai +foo*", flags);
1063 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((mai@1 AND WILDCARD SYNONYM foo))");
1064 qobj = qp.parse_query("-foo* main", flags);
1065 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@2 AND_NOT WILDCARD SYNONYM foo))");
1066 qobj = qp.parse_query("main -foo*", flags);
1067 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@1 AND_NOT WILDCARD SYNONYM foo))");
1068 qobj = qp.parse_query("main -foo* -bar", flags);
1069 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@1 AND_NOT (WILDCARD SYNONYM foo OR bar@3)))");
1070 qobj = qp.parse_query("main -bar -foo*", flags);
1071 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@1 AND_NOT (bar@2 OR WILDCARD SYNONYM foo)))");
1072 // Check with OP_AND too.
1073 qp.set_default_op(Xapian::Query::OP_AND);
1074 qobj = qp.parse_query("foo* main", flags);
1075 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM foo AND main@2))");
1076 qobj = qp.parse_query("main foo*", flags);
1077 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@1 AND WILDCARD SYNONYM foo))");
1078 qp.set_default_op(Xapian::Query::OP_AND);
1079 qobj = qp.parse_query("+foo* main", flags);
1080 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM foo AND main@2))");
1081 qobj = qp.parse_query("main +foo*", flags);
1082 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@1 AND WILDCARD SYNONYM foo))");
1083 qobj = qp.parse_query("-foo* main", flags);
1084 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@2 AND_NOT WILDCARD SYNONYM foo))");
1085 qobj = qp.parse_query("main -foo*", flags);
1086 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@1 AND_NOT WILDCARD SYNONYM foo))");
1087 // Check empty wildcard followed by negation.
1088 qobj = qp.parse_query("foo* -main", Xapian::QueryParser::FLAG_LOVEHATE|Xapian::QueryParser::FLAG_WILDCARD);
1089 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM foo AND_NOT main@2))");
1090 // Regression test for bug#484 fixed in 1.2.1 and 1.0.21.
1091 qobj = qp.parse_query("abc muscl* main", flags);
1092 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((abc@1 AND WILDCARD SYNONYM muscl AND main@3))");
1095 // Test right truncation with prefixes.
1096 DEFINE_TESTCASE(qp_flag_wildcard2, backend) {
1097 Xapian::Database db = get_database("qp_flag_wildcard2",
1098 [](Xapian::WritableDatabase& wdb,
1099 const string&) {
1100 Xapian::Document doc;
1101 doc.add_term("Aheinlein");
1102 doc.add_term("Ahuxley");
1103 doc.add_term("hello");
1104 wdb.add_document(doc);
1106 Xapian::QueryParser qp;
1107 qp.set_database(db);
1108 qp.add_prefix("author", "A");
1109 Xapian::Query qobj;
1110 qobj = qp.parse_query("author:h*", Xapian::QueryParser::FLAG_WILDCARD);
1111 TEST_STRINGS_EQUAL(qobj.get_description(), "Query(WILDCARD SYNONYM Ah)");
1112 qobj = qp.parse_query("author:h* test", Xapian::QueryParser::FLAG_WILDCARD);
1113 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM Ah OR test@2))");
1116 static void
1117 test_qp_flag_wildcard3_helper(const Xapian::Database &db,
1118 Xapian::termcount max_expansion,
1119 const string & query_string)
1121 Xapian::QueryParser qp;
1122 qp.set_database(db);
1123 qp.set_max_expansion(max_expansion);
1124 Xapian::Enquire e(db);
1125 e.set_query(qp.parse_query(query_string, Xapian::QueryParser::FLAG_WILDCARD));
1126 // The exception for expanding too much may happen at parse time or later
1127 // so we need to calculate the MSet too.
1128 e.get_mset(0, 10);
1131 // Test right truncation with a limit on expansion.
1132 DEFINE_TESTCASE(qp_flag_wildcard3, backend) {
1133 Xapian::Database db = get_database("qp_flag_wildcard3",
1134 [](Xapian::WritableDatabase& wdb,
1135 const string&) {
1136 Xapian::Document doc;
1137 doc.add_term("abc");
1138 doc.add_term("main");
1139 doc.add_term("muscat");
1140 doc.add_term("muscle");
1141 doc.add_term("musclebound");
1142 doc.add_term("muscular");
1143 doc.add_term("mutton");
1144 wdb.add_document(doc);
1147 // Test that a max of 0 doesn't set a limit.
1148 test_qp_flag_wildcard3_helper(db, 0, "z*");
1149 test_qp_flag_wildcard3_helper(db, 0, "m*");
1151 // These cases should expand to the limit given.
1152 test_qp_flag_wildcard3_helper(db, 1, "z*");
1153 test_qp_flag_wildcard3_helper(db, 1, "ab*");
1154 test_qp_flag_wildcard3_helper(db, 2, "muscle*");
1155 test_qp_flag_wildcard3_helper(db, 4, "musc*");
1156 test_qp_flag_wildcard3_helper(db, 4, "mus*");
1157 test_qp_flag_wildcard3_helper(db, 5, "mu*");
1158 test_qp_flag_wildcard3_helper(db, 6, "m*");
1160 // These cases should expand to one more than the limit.
1161 TEST_EXCEPTION(Xapian::WildcardError,
1162 test_qp_flag_wildcard3_helper(db, 1, "muscle*"));
1163 TEST_EXCEPTION(Xapian::WildcardError,
1164 test_qp_flag_wildcard3_helper(db, 3, "musc*"));
1165 TEST_EXCEPTION(Xapian::WildcardError,
1166 test_qp_flag_wildcard3_helper(db, 3, "mus*"));
1167 TEST_EXCEPTION(Xapian::WildcardError,
1168 test_qp_flag_wildcard3_helper(db, 4, "mu*"));
1169 TEST_EXCEPTION(Xapian::WildcardError,
1170 test_qp_flag_wildcard3_helper(db, 5, "m*"));
1173 /// Feature test for set_min_wildcard_prefix().
1174 DEFINE_TESTCASE(qp_flag_wildcard4, !backend) {
1175 struct qp_flag_wildcard4_test {
1176 unsigned min_len;
1177 const char* query_string;
1178 const char* expectw;
1179 const char* expectp;
1182 static const qp_flag_wildcard4_test testcases[] = {
1183 {0, "", "<alldocuments>", ""},
1184 // Perhaps set_min_wildcard_prefix() shouldn't be applied to a lone "*"
1185 // wildcard which converts to <alldocuments>, but whether than happens
1186 // depends on what prefixes are active so doing that seems more
1187 // confusing than not.
1188 {1, "", NULL, ""},
1189 {0, "m", "WILDCARD SYNONYM m*", NULL},
1190 {1, "m", "WILDCARD SYNONYM m*", NULL},
1191 {1, "ê", "WILDCARD SYNONYM ê*", NULL},
1192 {2, "m", NULL, "m@1"},
1193 {2, "ê", NULL, "ê@1"},
1194 {2, "\xe0\xa1\xa2", NULL, "\xe0\xa1\xa2@1"},
1195 {2, "\xf0\x90\xb3\x97", NULL, "\xf0\x90\xb3\x97@1"},
1196 {2, "mu", "WILDCARD SYNONYM mu*", NULL},
1197 {2, "mus", "WILDCARD SYNONYM mus*", NULL},
1198 {3, "mus", "WILDCARD SYNONYM mus*", NULL},
1199 {2, "\xf0\x90\xb3\x97\xf0\x90\xb3\x83", "WILDCARD SYNONYM \xf0\x90\xb3\x97\xf0\x90\xb3\x83*", NULL},
1200 {4, "mus", NULL, "mus@1"},
1201 {3, "\xf0\x90\xb3\x97\xf0\x90\xb3\x83", NULL, "\xf0\x90\xb3\x97\xf0\x90\xb3\x83@1"},
1204 constexpr auto FLAG_PARTIAL = Xapian::QueryParser::FLAG_PARTIAL;
1205 constexpr auto FLAG_WILDCARD = Xapian::QueryParser::FLAG_WILDCARD;
1206 constexpr auto FLAG_WILDCARD_GLOB = Xapian::QueryParser::FLAG_WILDCARD_GLOB;
1208 Xapian::QueryParser qp;
1210 for (const auto& test : testcases) {
1211 tout << test.min_len << ' ' << test.query_string << '\n';
1212 qp.set_min_wildcard_prefix(test.min_len, FLAG_WILDCARD | FLAG_PARTIAL);
1214 string query_string = string(test.query_string) + '*';
1215 if (test.expectw) {
1216 string expect = "Query(";
1217 // FLAG_WILDCARD doesn't expand a lone "*".
1218 if (test.query_string[0]) {
1219 expect += test.expectw;
1220 // OP_WILDCARD query descriptions don't include the "*".
1221 if (expect.back() == '*') expect.pop_back();
1223 expect += ')';
1224 Xapian::Query q = qp.parse_query(query_string, FLAG_WILDCARD);
1225 TEST_STRINGS_EQUAL(q.get_description(), expect);
1227 string expect_e = "Query(";
1228 expect_e += test.expectw;
1229 expect_e += ')';
1230 q = qp.parse_query(query_string, FLAG_WILDCARD_GLOB);
1231 TEST_STRINGS_EQUAL(q.get_description(), expect_e);
1232 } else {
1233 // If expectw is NULL, QueryParserError should be thrown.
1235 if (test.query_string[0]) {
1236 TEST_EXCEPTION(Xapian::QueryParserError,
1237 qp.parse_query(query_string, FLAG_WILDCARD));
1240 TEST_EXCEPTION(Xapian::QueryParserError,
1241 qp.parse_query(query_string, FLAG_WILDCARD_GLOB));
1244 string expect;
1245 if (test.expectp) {
1246 expect = "Query(";
1247 expect += test.expectp;
1248 expect += ')';
1249 } else {
1250 // If expectp is NULL, the partial result is expectw but with a
1251 // term ORed in.
1252 expect = "Query((";
1253 expect += test.expectw;
1254 expect.pop_back();
1255 size_t end = expect.size();
1256 size_t begin = expect.find_last_of(' ') + 1;
1257 expect += " OR ";
1258 expect.append(expect, begin, end - begin);
1259 expect += "@1))";
1262 auto q = qp.parse_query(test.query_string, FLAG_PARTIAL);
1263 TEST_STRINGS_EQUAL(q.get_description(), expect);
1267 static void
1268 gen_qp_flag_partial1_db(Xapian::WritableDatabase& db,
1269 const string&)
1271 Xapian::Document doc;
1272 Xapian::Stem stemmer("english");
1273 doc.add_term("abc");
1274 doc.add_term("main");
1275 doc.add_term("muscat");
1276 doc.add_term("muscle");
1277 doc.add_term("musclebound");
1278 doc.add_term("muscular");
1279 doc.add_term("mutton");
1280 doc.add_term("Z" + stemmer("outside"));
1281 doc.add_term("Z" + stemmer("out"));
1282 doc.add_term("outside");
1283 doc.add_term("out");
1284 doc.add_term("XTcove");
1285 doc.add_term("XTcows");
1286 doc.add_term("XTcowl");
1287 doc.add_term("XTcox");
1288 doc.add_term("ZXTcow");
1289 doc.add_term("XONEpartial");
1290 doc.add_term("XONEpartial2");
1291 doc.add_term("XTWOpartial3");
1292 doc.add_term("XTWOpartial4");
1293 db.add_document(doc);
1296 // Test partial queries.
1297 DEFINE_TESTCASE(qp_flag_partial1, backend) {
1298 Xapian::Database db = get_database("qp_flag_partial1",
1299 gen_qp_flag_partial1_db);
1300 Xapian::Stem stemmer("english");
1301 Xapian::QueryParser qp;
1302 qp.set_database(db);
1303 qp.set_stemmer(stemmer);
1304 qp.set_stemming_strategy(Xapian::QueryParser::STEM_SOME);
1305 qp.add_prefix("title", "XT");
1306 qp.add_prefix("double", "XONE");
1307 qp.add_prefix("double", "XTWO");
1309 // Default minimum length for partial term is 2 bytes.
1310 Xapian::Query qobj = qp.parse_query("a", Xapian::QueryParser::FLAG_PARTIAL);
1311 TEST_STRINGS_EQUAL(qobj.get_description(), "Query(Za@1)");
1312 qobj = qp.parse_query("o", Xapian::QueryParser::FLAG_PARTIAL);
1313 TEST_STRINGS_EQUAL(qobj.get_description(), "Query(Zo@1)");
1315 // Check behaviour with unstemmed terms
1316 qobj = qp.parse_query("ab", Xapian::QueryParser::FLAG_PARTIAL);
1317 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM ab OR Zab@1))");
1318 qobj = qp.parse_query("muscle", Xapian::QueryParser::FLAG_PARTIAL);
1319 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM muscle OR Zmuscl@1))");
1320 qobj = qp.parse_query("meat", Xapian::QueryParser::FLAG_PARTIAL);
1321 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM meat OR Zmeat@1))");
1322 qobj = qp.parse_query("musc", Xapian::QueryParser::FLAG_PARTIAL);
1323 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM musc OR Zmusc@1))");
1324 qobj = qp.parse_query("mutt", Xapian::QueryParser::FLAG_PARTIAL);
1325 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM mutt OR Zmutt@1))");
1326 qobj = qp.parse_query("abc musc", Xapian::QueryParser::FLAG_PARTIAL);
1327 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((Zabc@1 OR (WILDCARD SYNONYM musc OR Zmusc@2)))");
1328 qobj = qp.parse_query("a* mutt", Xapian::QueryParser::FLAG_PARTIAL | Xapian::QueryParser::FLAG_WILDCARD);
1329 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM a OR (WILDCARD SYNONYM mutt OR Zmutt@2)))");
1331 // Check behaviour with stemmed terms, and stem strategy STEM_SOME.
1332 qobj = qp.parse_query("ou", Xapian::QueryParser::FLAG_PARTIAL);
1333 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM ou OR Zou@1))");
1334 qobj = qp.parse_query("out", Xapian::QueryParser::FLAG_PARTIAL);
1335 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM out OR Zout@1))");
1336 qobj = qp.parse_query("outs", Xapian::QueryParser::FLAG_PARTIAL);
1337 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outs OR Zout@1))");
1338 qobj = qp.parse_query("outsi", Xapian::QueryParser::FLAG_PARTIAL);
1339 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outsi OR Zoutsi@1))");
1340 qobj = qp.parse_query("outsid", Xapian::QueryParser::FLAG_PARTIAL);
1341 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outsid OR Zoutsid@1))");
1342 qobj = qp.parse_query("outside", Xapian::QueryParser::FLAG_PARTIAL);
1343 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outside OR Zoutsid@1))");
1345 // Check behaviour with capitalised terms, and stem strategy STEM_SOME.
1346 qobj = qp.parse_query("Out", Xapian::QueryParser::FLAG_PARTIAL);
1347 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM out OR out@1))");
1348 qobj = qp.parse_query("Outs", Xapian::QueryParser::FLAG_PARTIAL);
1349 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outs OR outs@1))");
1350 qobj = qp.parse_query("Outside", Xapian::QueryParser::FLAG_PARTIAL);
1351 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outside OR outside@1))");
1352 // FIXME: Used to be this, but we aren't currently doing this change:
1353 // TEST_STRINGS_EQUAL(qobj.get_description(), "Query(outside@1#2)");
1355 // And now with stemming strategy STEM_SOME_FULL_POS.
1356 qp.set_stemming_strategy(Xapian::QueryParser::STEM_SOME_FULL_POS);
1357 qobj = qp.parse_query("Out", Xapian::QueryParser::FLAG_PARTIAL);
1358 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM out OR out@1))");
1359 qobj = qp.parse_query("Outs", Xapian::QueryParser::FLAG_PARTIAL);
1360 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outs OR outs@1))");
1361 qobj = qp.parse_query("Outside", Xapian::QueryParser::FLAG_PARTIAL);
1362 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outside OR outside@1))");
1364 // And now with stemming strategy STEM_ALL.
1365 qp.set_stemming_strategy(Xapian::QueryParser::STEM_ALL);
1366 qobj = qp.parse_query("Out", Xapian::QueryParser::FLAG_PARTIAL);
1367 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM out OR out@1))");
1368 qobj = qp.parse_query("Outs", Xapian::QueryParser::FLAG_PARTIAL);
1369 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outs OR out@1))");
1370 qobj = qp.parse_query("Outside", Xapian::QueryParser::FLAG_PARTIAL);
1371 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outside OR outsid@1))");
1373 // And now with stemming strategy STEM_ALL_Z.
1374 qp.set_stemming_strategy(Xapian::QueryParser::STEM_ALL_Z);
1375 qobj = qp.parse_query("Out", Xapian::QueryParser::FLAG_PARTIAL);
1376 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM out OR Zout@1))");
1377 qobj = qp.parse_query("Outs", Xapian::QueryParser::FLAG_PARTIAL);
1378 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outs OR Zout@1))");
1379 qobj = qp.parse_query("Outside", Xapian::QueryParser::FLAG_PARTIAL);
1380 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outside OR Zoutsid@1))");
1382 // Check handling of a case with a prefix.
1383 qp.set_stemming_strategy(Xapian::QueryParser::STEM_SOME);
1384 qobj = qp.parse_query("title:cow", Xapian::QueryParser::FLAG_PARTIAL);
1385 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM XTcow OR ZXTcow@1))");
1386 qobj = qp.parse_query("title:cows", Xapian::QueryParser::FLAG_PARTIAL);
1387 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM XTcows OR ZXTcow@1))");
1388 qobj = qp.parse_query("title:Cow", Xapian::QueryParser::FLAG_PARTIAL);
1389 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM XTcow OR XTcow@1))");
1390 qobj = qp.parse_query("title:Cows", Xapian::QueryParser::FLAG_PARTIAL);
1391 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM XTcows OR XTcows@1))");
1392 // FIXME: Used to be this, but we aren't currently doing this change:
1393 // TEST_STRINGS_EQUAL(qobj.get_description(), "Query(XTcows@1#2)");
1395 // Regression test - the initial version of the multi-prefix code would
1396 // inflate the wqf of the "parsed as normal" version of a partial term
1397 // by multiplying it by the number of prefixes mapped to.
1398 qobj = qp.parse_query("double:vision", Xapian::QueryParser::FLAG_PARTIAL);
1399 TEST_STRINGS_EQUAL(qobj.get_description(), "Query(((WILDCARD OR XONEvision SYNONYM WILDCARD OR XTWOvision) OR (ZXONEvision@1 SYNONYM ZXTWOvision@1)))");
1401 // Test handling of FLAG_PARTIAL when there's more than one prefix.
1402 qobj = qp.parse_query("double:part", Xapian::QueryParser::FLAG_PARTIAL);
1403 TEST_STRINGS_EQUAL(qobj.get_description(), "Query(((WILDCARD OR XONEpart SYNONYM WILDCARD OR XTWOpart) OR (ZXONEpart@1 SYNONYM ZXTWOpart@1)))");
1405 // Test handling of FLAG_PARTIAL when there's more than one prefix, without
1406 // stemming.
1407 qp.set_stemming_strategy(Xapian::QueryParser::STEM_NONE);
1408 qobj = qp.parse_query("double:part", Xapian::QueryParser::FLAG_PARTIAL);
1409 TEST_STRINGS_EQUAL(qobj.get_description(), "Query(((WILDCARD OR XONEpart SYNONYM WILDCARD OR XTWOpart) OR (XONEpart@1 SYNONYM XTWOpart@1)))");
1410 qobj = qp.parse_query("double:partial", Xapian::QueryParser::FLAG_PARTIAL);
1411 TEST_STRINGS_EQUAL(qobj.get_description(), "Query(((WILDCARD OR XONEpartial SYNONYM WILDCARD OR XTWOpartial) OR (XONEpartial@1 SYNONYM XTWOpartial@1)))");
1413 // Set minimum length to 1 byte and retest the shortest cases.
1414 qp.set_stemming_strategy(Xapian::QueryParser::STEM_SOME);
1415 qp.set_min_wildcard_prefix(1, Xapian::QueryParser::FLAG_PARTIAL);
1416 qobj = qp.parse_query("a", Xapian::QueryParser::FLAG_PARTIAL);
1417 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM a OR Za@1))");
1418 qobj = qp.parse_query("o", Xapian::QueryParser::FLAG_PARTIAL);
1419 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM o OR Zo@1))");
1422 /// Feature test for fuzzy matching.
1423 DEFINE_TESTCASE(qp_flag_fuzzy1, !backend) {
1424 static const struct { const char* q; const char* expect; } testcases[] = {
1425 { "musket~", "EDIT_DISTANCE SYNONYM musket~2" },
1426 { "musket~3", "EDIT_DISTANCE SYNONYM musket~3" },
1427 { "musket~0.5", "EDIT_DISTANCE SYNONYM musket~3" },
1428 // Check that fuzzy matching work with +terms.
1429 { "+mail~ basket", "(EDIT_DISTANCE SYNONYM mail~2 AND_MAYBE basket@2)" },
1430 { "foo~ main", "(EDIT_DISTANCE SYNONYM foo~2 OR main@2)" },
1431 { "main foo~", "(main@1 OR EDIT_DISTANCE SYNONYM foo~2)" },
1432 { "main +foo~", "(EDIT_DISTANCE SYNONYM foo~2 AND_MAYBE main@1)" },
1433 { "foo~ +main", "(main@2 AND_MAYBE EDIT_DISTANCE SYNONYM foo~2)" },
1434 { "+main foo~", "(main@1 AND_MAYBE EDIT_DISTANCE SYNONYM foo~2)" },
1435 { "+foo~ +main", "(EDIT_DISTANCE SYNONYM foo~2 AND main@2)" },
1436 { "+main +foo~", "(main@1 AND EDIT_DISTANCE SYNONYM foo~2)" },
1437 { "foo~ mai", "(EDIT_DISTANCE SYNONYM foo~2 OR mai@2)" },
1438 { "mai foo~", "(mai@1 OR EDIT_DISTANCE SYNONYM foo~2)" },
1439 { "+foo~ mai", "(EDIT_DISTANCE SYNONYM foo~2 AND_MAYBE mai@2)" },
1440 { "mai +foo~", "(EDIT_DISTANCE SYNONYM foo~2 AND_MAYBE mai@1)" },
1441 { "foo~ +mai", "(mai@2 AND_MAYBE EDIT_DISTANCE SYNONYM foo~2)" },
1442 { "+mai foo~", "(mai@1 AND_MAYBE EDIT_DISTANCE SYNONYM foo~2)" },
1443 { "+foo~ +mai", "(EDIT_DISTANCE SYNONYM foo~2 AND mai@2)" },
1444 { "+mai +foo~", "(mai@1 AND EDIT_DISTANCE SYNONYM foo~2)" },
1445 { "-foo~ main", "(main@2 AND_NOT EDIT_DISTANCE SYNONYM foo~2)" },
1446 { "main -foo~", "(main@1 AND_NOT EDIT_DISTANCE SYNONYM foo~2)" },
1447 { "main -foo~ -bar", "(main@1 AND_NOT (EDIT_DISTANCE SYNONYM foo~2 OR bar@3))" },
1448 { "main -bar -foo~", "(main@1 AND_NOT (bar@2 OR EDIT_DISTANCE SYNONYM foo~2))" },
1449 // Switch default_op to OP_AND.
1450 { NULL, NULL },
1451 { "foo~ main", "(EDIT_DISTANCE SYNONYM foo~2 AND main@2)" },
1452 { "main foo~", "(main@1 AND EDIT_DISTANCE SYNONYM foo~2)" },
1453 { "+foo~ main", "(EDIT_DISTANCE SYNONYM foo~2 AND main@2)" },
1454 { "main +foo~", "(main@1 AND EDIT_DISTANCE SYNONYM foo~2)" },
1455 { "-foo~ main", "(main@2 AND_NOT EDIT_DISTANCE SYNONYM foo~2)" },
1456 { "main -foo~", "(main@1 AND_NOT EDIT_DISTANCE SYNONYM foo~2)" },
1457 // Check empty fuzzy followed by negation.
1458 { "xyzzy~ -main", "(EDIT_DISTANCE SYNONYM xyzzy~2 AND_NOT main@2)" },
1459 { "abc muscl~ main", "(abc@1 AND EDIT_DISTANCE SYNONYM muscl~2 AND main@3)" },
1462 Xapian::QueryParser qp;
1463 unsigned flags = Xapian::QueryParser::FLAG_FUZZY |
1464 Xapian::QueryParser::FLAG_LOVEHATE;
1466 for (auto&& t : testcases) {
1467 if (t.q == NULL) {
1468 qp.set_default_op(Xapian::Query::OP_AND);
1469 continue;
1471 tout << t.q << '\n';
1472 auto qobj = qp.parse_query(t.q, flags);
1473 string expect = "Query(";
1474 expect += t.expect;
1475 expect += ")";
1476 TEST_STRINGS_EQUAL(qobj.get_description(), expect);
1480 /// Feature test of fuzzy matching with prefixes.
1481 DEFINE_TESTCASE(qp_flag_fuzzy2, !backend) {
1482 static const struct { const char* q; const char* expect; } testcases[] = {
1483 { "author:huxly~", "EDIT_DISTANCE SYNONYM Ahuxly~2 fixed_prefix_len=1" },
1484 { "author:huxly~ test", "(EDIT_DISTANCE SYNONYM Ahuxly~2 fixed_prefix_len=1 OR test@2)" },
1487 Xapian::QueryParser qp;
1488 qp.add_prefix("author", "A");
1489 for (auto&& t : testcases) {
1490 tout << t.q << '\n';
1491 auto qobj = qp.parse_query(t.q, qp.FLAG_FUZZY);
1492 string expect = "Query(";
1493 expect += t.expect;
1494 expect += ")";
1495 TEST_STRINGS_EQUAL(qobj.get_description(), expect);
1499 static void
1500 test_qp_flag_fuzzy3_helper(const Xapian::Database& db,
1501 Xapian::termcount max_expansion,
1502 const string& query_string)
1504 Xapian::QueryParser qp;
1505 qp.set_database(db);
1506 if (max_expansion == Xapian::termcount(-1)) {
1507 tout << "testing fuzzy query '" << query_string << "' with default "
1508 "limit (which should be 0)\n";
1509 max_expansion = 0;
1510 } else {
1511 tout << "testing fuzzy query '" << query_string << "' with limit "
1512 << max_expansion << '\n';
1513 qp.set_max_expansion(max_expansion);
1515 Xapian::Enquire e(db);
1516 e.set_query(qp.parse_query(query_string, qp.FLAG_FUZZY));
1517 e.get_mset(0, 10);
1518 if (max_expansion <= 1) return;
1520 // Test that a limit one lower throws WildcardError.
1521 qp.set_max_expansion(max_expansion - 1);
1522 e.set_query(qp.parse_query(query_string, qp.FLAG_FUZZY));
1523 TEST_EXCEPTION(Xapian::WildcardError, e.get_mset(0, 10));
1526 /// Test fuzzy matching with a limit on expansion.
1527 DEFINE_TESTCASE(qp_flag_fuzzy3, backend) {
1528 Xapian::Database db = get_database("qp_flag_fuzzy3",
1529 [](Xapian::WritableDatabase& wdb,
1530 const string&) {
1531 Xapian::Document doc;
1532 doc.add_term("abc");
1533 doc.add_term("main");
1534 doc.add_term("marcel");
1535 doc.add_term("muscadet");
1536 doc.add_term("muscae");
1537 doc.add_term("muscat");
1538 doc.add_term("muscid");
1539 doc.add_term("muscle");
1540 doc.add_term("muscly");
1541 doc.add_term("musclebound");
1542 doc.add_term("muscular");
1543 doc.add_term("mutton");
1544 doc.add_term("tusche");
1545 wdb.add_document(doc);
1548 // Test that the default is no limit.
1549 test_qp_flag_fuzzy3_helper(db, Xapian::termcount(-1), "zzz~");
1550 test_qp_flag_fuzzy3_helper(db, Xapian::termcount(-1), "zzz~4");
1551 test_qp_flag_fuzzy3_helper(db, Xapian::termcount(-1), "muscle~");
1553 // Test that a max of 0 doesn't set a limit.
1554 test_qp_flag_fuzzy3_helper(db, 0, "zzz~");
1555 test_qp_flag_fuzzy3_helper(db, 0, "zzz~4");
1556 test_qp_flag_fuzzy3_helper(db, 0, "muscle~");
1558 // These cases should expand to the limit given.
1559 test_qp_flag_fuzzy3_helper(db, 1, "azz~");
1560 test_qp_flag_fuzzy3_helper(db, 1, "bac~.4");
1561 test_qp_flag_fuzzy3_helper(db, 6, "muscle~");
1562 test_qp_flag_fuzzy3_helper(db, 6, "muscel~");
1563 test_qp_flag_fuzzy3_helper(db, 9, "muscel~3");
1566 // Tests for document counts for wildcard queries.
1567 // Regression test for bug fixed in 1.0.0.
1568 DEFINE_TESTCASE(wildquery1, backend) {
1569 Xapian::QueryParser queryparser;
1570 unsigned flags = Xapian::QueryParser::FLAG_WILDCARD |
1571 Xapian::QueryParser::FLAG_LOVEHATE;
1572 queryparser.set_stemmer(Xapian::Stem("english"));
1573 queryparser.set_stemming_strategy(Xapian::QueryParser::STEM_ALL);
1574 Xapian::Database db = get_database("apitest_simpledata");
1575 queryparser.set_database(db);
1576 Xapian::Enquire enquire(db);
1578 Xapian::Query qobj = queryparser.parse_query("th*", flags);
1579 tout << qobj.get_description() << '\n';
1580 enquire.set_query(qobj);
1581 Xapian::MSet mymset = enquire.get_mset(0, 10);
1582 // Check that 6 documents were returned.
1583 TEST_MSET_SIZE(mymset, 6);
1585 qobj = queryparser.parse_query("notindb* \"this\"", flags);
1586 tout << qobj.get_description() << '\n';
1587 enquire.set_query(qobj);
1588 mymset = enquire.get_mset(0, 10);
1589 // Check that 6 documents were returned.
1590 TEST_MSET_SIZE(mymset, 6);
1592 qobj = queryparser.parse_query("+notindb* \"this\"", flags);
1593 tout << qobj.get_description() << '\n';
1594 enquire.set_query(qobj);
1595 mymset = enquire.get_mset(0, 10);
1596 // Check that 0 documents were returned.
1597 TEST_MSET_SIZE(mymset, 0);
1600 // Tests for extended wildcarded queries.
1601 DEFINE_TESTCASE(wildquery2, !backend) {
1602 Xapian::QueryParser queryparser;
1603 unsigned flags = Xapian::QueryParser::FLAG_DEFAULT |
1604 Xapian::QueryParser::FLAG_WILDCARD_GLOB;
1605 queryparser.set_stemmer(Xapian::Stem("english"));
1606 queryparser.set_stemming_strategy(Xapian::QueryParser::STEM_ALL);
1608 Xapian::Query qobj;
1609 qobj = queryparser.parse_query("*th", flags);
1610 TEST_EQUAL(qobj.get_description(), "Query(WILDCARD SYNONYM *th)");
1612 qobj = queryparser.parse_query("?th", flags);
1613 TEST_EQUAL(qobj.get_description(), "Query(WILDCARD SYNONYM ?th)");
1615 qobj = queryparser.parse_query("?th*", flags);
1616 TEST_EQUAL(qobj.get_description(), "Query(WILDCARD SYNONYM ?th*)");
1618 qobj = queryparser.parse_query("*th", flags);
1619 TEST_EQUAL(qobj.get_description(), "Query(WILDCARD SYNONYM *th)");
1621 qobj = queryparser.parse_query("?th", flags);
1622 TEST_EQUAL(qobj.get_description(), "Query(WILDCARD SYNONYM ?th)");
1624 qobj = queryparser.parse_query("foo *?x?", flags);
1625 TEST_EQUAL(qobj.get_description(),
1626 "Query((foo@1 OR WILDCARD SYNONYM *?x?))");
1628 qobj = queryparser.parse_query("* ?", flags);
1629 TEST_EQUAL(qobj.get_description(),
1630 "Query((<alldocuments> OR WILDCARD SYNONYM ?))");
1632 qobj = queryparser.parse_query("** test", flags);
1633 TEST_EQUAL(qobj.get_description(),
1634 "Query((<alldocuments> OR test@2))");
1636 qobj = queryparser.parse_query("?? test", flags);
1637 TEST_EQUAL(qobj.get_description(),
1638 "Query((WILDCARD SYNONYM ?""? OR test@2))");
1640 qobj = queryparser.parse_query("??* test", flags);
1641 TEST_EQUAL(qobj.get_description(),
1642 "Query((WILDCARD SYNONYM ??* OR test@2))");
1644 qobj = queryparser.parse_query("*?* test", flags);
1645 TEST_EQUAL(qobj.get_description(),
1646 "Query((<alldocuments> OR test@2))");
1649 DEFINE_TESTCASE(qp_flag_bool_any_case1, !backend) {
1650 using Xapian::QueryParser;
1651 Xapian::QueryParser qp;
1652 Xapian::Query qobj;
1653 qobj = qp.parse_query("to and fro", QueryParser::FLAG_BOOLEAN | QueryParser::FLAG_BOOLEAN_ANY_CASE);
1654 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((to@1 AND fro@2))");
1655 qobj = qp.parse_query("to and fro", QueryParser::FLAG_BOOLEAN);
1656 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((to@1 OR and@2 OR fro@3))");
1657 // Regression test for bug in 0.9.4 and earlier.
1658 qobj = qp.parse_query("to And fro", QueryParser::FLAG_BOOLEAN | QueryParser::FLAG_BOOLEAN_ANY_CASE);
1659 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((to@1 AND fro@2))");
1660 qobj = qp.parse_query("to And fro", QueryParser::FLAG_BOOLEAN);
1661 TEST_STRINGS_EQUAL(qobj.get_description(), "Query((to@1 OR and@2 OR fro@3))");
1664 static const test test_stop_queries[] = {
1665 { "test the queryparser", "(test@1 AND queryparser@3)" },
1666 // Regression test for bug in 0.9.6 and earlier. This would fail to
1667 // parse.
1668 { "test AND the AND queryparser", "(test@1 AND the@2 AND queryparser@3)" },
1669 // 0.9.6 and earlier ignored a stopword even if it was the only term.
1670 // More recent versions don't ever treat a single term as a stopword.
1671 { "the", "the@1" },
1672 // 1.2.2 and earlier ignored an all-stopword query with multiple terms,
1673 // which prevents 'to be or not to be' for being searchable unless the
1674 // user made it into a phrase query or prefixed all terms with '+'
1675 // (ticket#245).
1676 { "an the a", "(an@1 AND the@2 AND a@3)" },
1677 // Regression test for bug in initial version of the patch for the
1678 // "all-stopword" case.
1679 { "the AND a an", "(the@1 AND (a@2 AND an@3))" },
1680 { NULL, NULL }
1683 DEFINE_TESTCASE(qp_stopper1, !backend) {
1684 Xapian::QueryParser qp;
1685 static const char * const stopwords[] = { "a", "an", "the" };
1686 Xapian::SimpleStopper stop(stopwords, stopwords + 3);
1687 qp.set_stopper(&stop);
1688 qp.set_default_op(Xapian::Query::OP_AND);
1689 for (const test *p = test_stop_queries; p->query; ++p) {
1690 string expect, parsed;
1691 if (p->expect)
1692 expect = p->expect;
1693 else
1694 expect = "parse error";
1695 try {
1696 Xapian::Query qobj = qp.parse_query(p->query);
1697 parsed = qobj.get_description();
1698 expect = string("Query(") + expect + ')';
1699 } catch (const Xapian::QueryParserError &e) {
1700 parsed = e.get_msg();
1701 } catch (const Xapian::Error &e) {
1702 parsed = e.get_description();
1703 } catch (...) {
1704 parsed = "Unknown exception!";
1706 tout << "Query: " << p->query << '\n';
1707 TEST_STRINGS_EQUAL(parsed, expect);
1711 static const test test_pure_not_queries[] = {
1712 { "NOT windows", "(0 * <alldocuments> AND_NOT Zwindow@1)" },
1713 { "a AND (NOT b)", "(Za@1 AND (0 * <alldocuments> AND_NOT Zb@2))" },
1714 { "AND NOT windows", "Syntax: <expression> AND NOT <expression>" },
1715 { "gordian NOT", "Syntax: <expression> NOT <expression>" },
1716 { "gordian AND NOT", "Syntax: <expression> AND NOT <expression>" },
1717 { NULL, NULL }
1720 DEFINE_TESTCASE(qp_flag_pure_not1, !backend) {
1721 using Xapian::QueryParser;
1722 Xapian::QueryParser qp;
1723 qp.set_stemmer(Xapian::Stem("english"));
1724 qp.set_stemming_strategy(QueryParser::STEM_SOME);
1725 for (const test *p = test_pure_not_queries; p->query; ++p) {
1726 string expect, parsed;
1727 if (p->expect)
1728 expect = p->expect;
1729 else
1730 expect = "parse error";
1731 try {
1732 Xapian::Query qobj = qp.parse_query(p->query,
1733 QueryParser::FLAG_BOOLEAN |
1734 QueryParser::FLAG_PURE_NOT);
1735 parsed = qobj.get_description();
1736 expect = string("Query(") + expect + ')';
1737 } catch (const Xapian::QueryParserError &e) {
1738 parsed = e.get_msg();
1739 } catch (const Xapian::Error &e) {
1740 parsed = e.get_description();
1741 } catch (...) {
1742 parsed = "Unknown exception!";
1744 tout << "Query: " << p->query << '\n';
1745 TEST_STRINGS_EQUAL(parsed, expect);
1749 // Debatable if this is a regression test or a feature test, as it's not
1750 // obvious is this was a bug fix or a new feature. Either way, it first
1751 // appeared in Xapian 1.0.0.
1752 DEFINE_TESTCASE(qp_unstem_boolean_prefix, !backend) {
1753 Xapian::QueryParser qp;
1754 qp.add_boolean_prefix("test", "XTEST");
1755 Xapian::Query q = qp.parse_query("hello test:foo");
1756 TEST_STRINGS_EQUAL(q.get_description(), "Query((hello@1 FILTER XTESTfoo))");
1757 Xapian::TermIterator u = qp.unstem_begin("XTESTfoo");
1758 TEST(u != qp.unstem_end("XTESTfoo"));
1759 TEST_EQUAL(*u, "test:foo");
1760 ++u;
1761 TEST(u == qp.unstem_end("XTESTfoo"));
1764 // Feature test for FLAG_ACCUMULATE.
1765 DEFINE_TESTCASE(qp_accumulate, !backend) {
1766 Xapian::QueryParser qp;
1767 Xapian::SimpleStopper stopper;
1768 stopper.add("a");
1769 stopper.add("the");
1770 qp.set_stopper(&stopper);
1771 qp.set_stemmer(Xapian::Stem("en"));
1772 qp.add_boolean_prefix("test", "XTEST");
1773 qp.add_prefix("foo", "XFOO");
1774 Xapian::Query q = qp.parse_query("a plains test:bools foo:fielded");
1775 tout << q.get_description() << '\n';
1777 Xapian::TermIterator t = qp.unstem_begin("Zplain");
1778 TEST(t != qp.unstem_end("Zplain"));
1779 TEST_EQUAL(*t, "plains");
1780 ++t;
1781 TEST(t == qp.unstem_end("Zplain"));
1784 Xapian::TermIterator t = qp.unstem_begin("XTESTbools");
1785 TEST(t != qp.unstem_end("XTESTbools"));
1786 TEST_EQUAL(*t, "test:bools");
1787 ++t;
1788 TEST(t == qp.unstem_end("XTESTbools"));
1791 Xapian::TermIterator t = qp.unstem_begin("ZXFOOfield");
1792 TEST(t != qp.unstem_end("ZXFOOfield"));
1793 TEST_EQUAL(*t, "fielded");
1794 ++t;
1795 TEST(t == qp.unstem_end("ZXFOOfield"));
1798 Xapian::TermIterator t = qp.stoplist_begin();
1799 TEST(t != qp.stoplist_end());
1800 TEST_EQUAL(*t, "a");
1801 ++t;
1802 TEST(t == qp.stoplist_end());
1804 q = qp.parse_query("the plain foo:fields",
1805 qp.FLAG_DEFAULT | qp.FLAG_ACCUMULATE);
1806 tout << q.get_description() << '\n';
1808 Xapian::TermIterator t = qp.unstem_begin("Zplain");
1809 TEST(t != qp.unstem_end("Zplain"));
1810 TEST_EQUAL(*t, "plains");
1811 ++t;
1812 TEST(t != qp.unstem_end("Zplain"));
1813 TEST_EQUAL(*t, "plain");
1814 ++t;
1815 TEST(t == qp.unstem_end("Zplain"));
1818 Xapian::TermIterator t = qp.unstem_begin("XTESTbools");
1819 TEST(t != qp.unstem_end("XTESTbools"));
1820 TEST_EQUAL(*t, "test:bools");
1821 ++t;
1822 TEST(t == qp.unstem_end("XTESTbools"));
1825 Xapian::TermIterator t = qp.unstem_begin("ZXFOOfield");
1826 TEST(t != qp.unstem_end("ZXFOOfield"));
1827 TEST_EQUAL(*t, "fielded");
1828 ++t;
1829 TEST(t != qp.unstem_end("ZXFOOfield"));
1830 TEST_EQUAL(*t, "fields");
1831 ++t;
1832 TEST(t == qp.unstem_end("ZXFOOfield"));
1835 Xapian::TermIterator t = qp.stoplist_begin();
1836 TEST(t != qp.stoplist_end());
1837 TEST_EQUAL(*t, "a");
1838 ++t;
1839 TEST(t != qp.stoplist_end());
1840 TEST_EQUAL(*t, "the");
1841 ++t;
1842 TEST(t == qp.stoplist_end());
1844 // Check things are reset without FLAG_ACCUMULATE.
1845 q = qp.parse_query("plains");
1846 tout << q.get_description() << '\n';
1848 Xapian::TermIterator t = qp.unstem_begin("Zplain");
1849 TEST(t != qp.unstem_end("Zplain"));
1850 TEST_EQUAL(*t, "plains");
1851 ++t;
1852 TEST(t == qp.unstem_end("Zplain"));
1855 Xapian::TermIterator t = qp.unstem_begin("XTESTboolean");
1856 TEST(t == qp.unstem_end("XTESTboolean"));
1859 Xapian::TermIterator t = qp.unstem_begin("ZXFOOfield");
1860 TEST(t == qp.unstem_end("ZXFOOfield"));
1863 Xapian::TermIterator t = qp.stoplist_begin();
1864 TEST(t == qp.stoplist_end());
1868 static const test test_value_range1_queries[] = {
1869 { "a..b", "VALUE_RANGE 1 a b" },
1870 { "$50..100", "VALUE_RANGE 1 $50 100" },
1871 { "$50..$99", "VALUE_RANGE 1 $50 $99" },
1872 { "$50..$100", "" }, // start > range
1873 { "02/03/1979..10/12/1980", "VALUE_RANGE 1 02/03/1979 10/12/1980" },
1874 { "a..b hello", "(hello@1 FILTER VALUE_RANGE 1 a b)" },
1875 { "hello a..b", "(hello@1 FILTER VALUE_RANGE 1 a b)" },
1876 { "hello a..b world", "((hello@1 OR world@2) FILTER VALUE_RANGE 1 a b)" },
1877 { "hello a..b test:foo", "(hello@1 FILTER (VALUE_RANGE 1 a b AND XTESTfoo))" },
1878 { "hello a..b test:foo test:bar", "(hello@1 FILTER (VALUE_RANGE 1 a b AND (XTESTfoo OR XTESTbar)))" },
1879 { "hello a..b c..d test:foo", "(hello@1 FILTER ((VALUE_RANGE 1 a b OR VALUE_RANGE 1 c d) AND XTESTfoo))" },
1880 { "hello a..b c..d test:foo test:bar", "(hello@1 FILTER ((VALUE_RANGE 1 a b OR VALUE_RANGE 1 c d) AND (XTESTfoo OR XTESTbar)))" },
1881 { "-5..7", "VALUE_RANGE 1 -5 7" },
1882 { "hello -5..7", "(hello@1 FILTER VALUE_RANGE 1 -5 7)" },
1883 { "-5..7 hello", "(hello@1 FILTER VALUE_RANGE 1 -5 7)" },
1884 { "\"time flies\" 09:00..12:30", "((time@1 PHRASE 2 flies@2) FILTER VALUE_RANGE 1 09:00 12:30)" },
1885 // Feature test for single-ended ranges (ticket#480):
1886 { "..b", "VALUE_LE 1 b" },
1887 { "a..", "VALUE_GE 1 a" },
1888 // Test for expanded set of characters allowed in range start:
1889 { "10:30+1300..11:00+1300", "VALUE_RANGE 1 10:30+1300 11:00+1300" },
1890 { NULL, NULL }
1893 // Simple test of RangeProcessor class.
1894 DEFINE_TESTCASE(qp_range1, !backend) {
1895 Xapian::QueryParser qp;
1896 qp.add_boolean_prefix("test", "XTEST");
1897 Xapian::RangeProcessor rp(1);
1898 qp.add_rangeprocessor(&rp);
1899 for (const test *p = test_value_range1_queries; p->query; ++p) {
1900 string expect, parsed;
1901 if (p->expect)
1902 expect = p->expect;
1903 else
1904 expect = "parse error";
1905 try {
1906 Xapian::Query qobj = qp.parse_query(p->query);
1907 parsed = qobj.get_description();
1908 expect = string("Query(") + expect + ')';
1909 } catch (const Xapian::QueryParserError &e) {
1910 parsed = e.get_msg();
1911 } catch (const Xapian::Error &e) {
1912 parsed = e.get_description();
1913 } catch (...) {
1914 parsed = "Unknown exception!";
1916 tout << "Query: " << p->query << '\n';
1917 TEST_STRINGS_EQUAL(parsed, expect);
1921 static const test test_value_range2_queries[] = {
1922 { "a..b", "VALUE_RANGE 3 a b" },
1923 { "1..12", "VALUE_RANGE 2 \\xa0 \\xae" },
1924 { "20070201..20070228", "VALUE_RANGE 1 20070201 20070228" },
1925 { "$10..20", "VALUE_RANGE 4 \\xad \\xb1" },
1926 { "$10..$20", "VALUE_RANGE 4 \\xad \\xb1" },
1927 // Feature test for single-ended ranges (ticket#480):
1928 { "$..20", "VALUE_LE 4 \\xb1" },
1929 { "..$20", "VALUE_LE 3 $20" }, // FIXME: probably should parse as $..20
1930 { "$10..", "VALUE_GE 4 \\xad" },
1931 { "12..42kg", "VALUE_RANGE 5 \\xae \\xb5@" },
1932 { "12kg..42kg", "VALUE_RANGE 5 \\xae \\xb5@" },
1933 { "12kg..42", "VALUE_RANGE 3 12kg 42" },
1934 { "10..$20", "" }, // start > end
1935 { "1999-03-12..2020-12-30", "VALUE_RANGE 1 19990312 20201230" },
1936 { "1999/03/12..2020/12/30", "VALUE_RANGE 1 19990312 20201230" },
1937 { "1999.03.12..2020.12.30", "VALUE_RANGE 1 19990312 20201230" },
1938 // Feature test for single-ended ranges (ticket#480):
1939 { "..2020.12.30", "VALUE_LE 1 20201230" },
1940 { "1999.03.12..", "VALUE_GE 1 19990312" },
1941 { "12/03/99..12/04/01", "VALUE_RANGE 1 19990312 20010412" },
1942 { "03-12-99..04-14-01", "VALUE_RANGE 1 19990312 20010414" },
1943 { "1/2/3..2/3/4", "VALUE_RANGE 1 20030201 20040302" },
1944 { "(test:a..test:b hello)", "(hello@1 FILTER VALUE_RANGE 3 test:a test:b)" },
1945 { "12..42kg 5..6kg 1..12", "0 * (VALUE_RANGE 2 \\xa0 \\xae AND (VALUE_RANGE 5 \\xae \\xb5@ OR VALUE_RANGE 5 \\xa9 \\xaa))" },
1946 // Check that a VRP which fails to match doesn't remove a prefix or suffix.
1947 // 1.0.13/1.1.1 and earlier got this wrong in some cases.
1948 { "$12a..13", "VALUE_RANGE 3 $12a 13" },
1949 { "$12..13b", "VALUE_RANGE 3 $12 13b" },
1950 { "$12..12kg", "VALUE_RANGE 3 $12 12kg" },
1951 { "12..b12kg", "VALUE_RANGE 3 12 b12kg" },
1952 { NULL, NULL }
1955 // Test chaining of RangeProcessor classes.
1956 DEFINE_TESTCASE(qp_range2, !backend) {
1957 using Xapian::RP_REPEATED;
1958 using Xapian::RP_SUFFIX;
1959 Xapian::QueryParser qp;
1960 qp.add_boolean_prefix("test", "XTEST");
1961 Xapian::DateRangeProcessor rp_date(1);
1962 Xapian::NumberRangeProcessor rp_num(2);
1963 Xapian::RangeProcessor rp_str(3);
1964 Xapian::NumberRangeProcessor rp_cash(4, "$", RP_REPEATED);
1965 Xapian::NumberRangeProcessor rp_weight(5, "kg", RP_SUFFIX|RP_REPEATED);
1966 qp.add_rangeprocessor(&rp_date);
1967 qp.add_rangeprocessor(&rp_num);
1968 qp.add_rangeprocessor(&rp_cash);
1969 qp.add_rangeprocessor(&rp_weight);
1970 qp.add_rangeprocessor(&rp_str);
1971 for (const test *p = test_value_range2_queries; p->query; ++p) {
1972 string expect, parsed;
1973 if (p->expect)
1974 expect = p->expect;
1975 else
1976 expect = "parse error";
1977 try {
1978 Xapian::Query qobj = qp.parse_query(p->query);
1979 parsed = qobj.get_description();
1980 expect = string("Query(") + expect + ')';
1981 } catch (const Xapian::QueryParserError &e) {
1982 parsed = e.get_msg();
1983 } catch (const Xapian::Error &e) {
1984 parsed = e.get_description();
1985 } catch (...) {
1986 parsed = "Unknown exception!";
1988 tout << "Query: " << p->query << '\n';
1989 TEST_STRINGS_EQUAL(parsed, expect);
1993 static void
1994 gen_qp_range3_db(Xapian::WritableDatabase& db, const string&)
1996 double low = -10;
1997 int steps = 60;
1998 double step = 0.5;
2000 for (int i = 0; i <= steps; ++i) {
2001 double v = low + i * step;
2002 Xapian::Document doc;
2003 doc.add_value(1, Xapian::sortable_serialise(v));
2004 db.add_document(doc);
2008 // Test NumberRangeProcessors with actual data.
2009 DEFINE_TESTCASE(qp_range3, backend) {
2010 double low = -10;
2011 int steps = 60;
2012 double step = 0.5;
2014 Xapian::Database db = get_database("qp_range3", gen_qp_range3_db);
2016 Xapian::NumberRangeProcessor rp_num(1);
2017 Xapian::QueryParser qp;
2018 qp.add_rangeprocessor(&rp_num);
2020 for (int j = 0; j <= steps; ++j) {
2021 double start = low + j * step;
2022 for (int k = 0; k <= steps; ++k) {
2023 double end = low + k * step;
2024 string query = str(start) + ".." + str(end);
2025 tout << "Query: " << query << '\n';
2026 Xapian::Query qobj = qp.parse_query(query);
2027 Xapian::Enquire enq(db);
2028 enq.set_query(qobj);
2029 Xapian::MSet mset = enq.get_mset(0, steps + 1);
2030 if (end < start) {
2031 TEST_EQUAL(mset.size(), 0);
2032 } else {
2033 TEST_EQUAL(mset.size(), 1u + (k - j));
2034 for (unsigned int m = 0; m != mset.size(); ++m) {
2035 double v = start + m * step;
2036 TEST_EQUAL(mset[m].get_document().get_value(1),
2037 Xapian::sortable_serialise(v));
2044 static const test test_value_range4_queries[] = {
2045 { "id:19254@foo..example.com", "0 * Q19254@foo..example.com" },
2046 { "hello:world", "0 * XHELLOworld" },
2047 { "hello:mum..world", "VALUE_RANGE 1 mum world" },
2048 { NULL, NULL }
2051 /** Test a boolean filter which happens to contain "..".
2053 * Regression test for bug fixed in 1.2.3.
2055 * Also test that the same prefix can be set for a range and filter.
2057 DEFINE_TESTCASE(qp_range4, !backend) {
2058 Xapian::QueryParser qp;
2059 qp.add_boolean_prefix("id", "Q");
2060 qp.add_boolean_prefix("hello", "XHELLO");
2061 Xapian::RangeProcessor rp_str(1, "hello:");
2062 qp.add_rangeprocessor(&rp_str);
2063 for (const test *p = test_value_range4_queries; p->query; ++p) {
2064 string expect, parsed;
2065 if (p->expect)
2066 expect = p->expect;
2067 else
2068 expect = "parse error";
2069 try {
2070 Xapian::Query qobj = qp.parse_query(p->query);
2071 parsed = qobj.get_description();
2072 expect = string("Query(") + expect + ')';
2073 } catch (const Xapian::QueryParserError &e) {
2074 parsed = e.get_msg();
2075 } catch (const Xapian::Error &e) {
2076 parsed = e.get_description();
2077 } catch (...) {
2078 parsed = "Unknown exception!";
2080 tout << "Query: " << p->query << '\n';
2081 TEST_STRINGS_EQUAL(parsed, expect);
2085 static const test test_unitrange1_queries[] = {
2086 { "financial report size:100K..1M", "((financial@1 OR report@2) FILTER VALUE_RANGE 1 \\xe0&@ \\xe04)" },
2087 { "size:1B..1G", "VALUE_RANGE 1 \\xa0 \\xe0\\x5c" },
2088 // Interpret this as size:10K..100K
2089 { "size:10..100K", "VALUE_RANGE 1 \\xd9 \\xe0&@" },
2090 // Feature test for single-ended ranges
2091 { "size:10K..", "VALUE_GE 1 \\xd9" },
2092 { "size:..2M", "VALUE_LE 1 \\xe08" },
2093 // Forbidden ranges
2094 { "size:10B..100", "Unknown range operation" },
2095 { "size:10..100", "Unknown range operation" },
2096 { "size:..100", "Unknown range operation" },
2097 { "size:10..", "Unknown range operation" },
2098 { NULL, NULL }
2101 // Simple Test of UnitRangeProcessor class.
2102 DEFINE_TESTCASE(qp_range5, !backend) {
2103 Xapian::QueryParser qp;
2104 Xapian::UnitRangeProcessor rp_size(1, "size:");
2105 qp.add_rangeprocessor(&rp_size);
2106 for (const test *p = test_unitrange1_queries; p->query; ++p) {
2107 string expect, parsed;
2108 if (p->expect)
2109 expect = p->expect;
2110 else
2111 expect = "parse error";
2112 try {
2113 Xapian::Query qobj = qp.parse_query(p->query);
2114 parsed = qobj.get_description();
2115 expect = string("Query(") + expect + ')';
2116 } catch (const Xapian::QueryParserError &e) {
2117 parsed = e.get_msg();
2118 } catch (const Xapian::Error &e) {
2119 parsed = e.get_description();
2120 } catch (...) {
2121 parsed = "Unknown exception!";
2123 tout << "Query: " << p->query << '\n';
2124 TEST_STRINGS_EQUAL(parsed, expect);
2128 static const test test_value_daterange1_queries[] = {
2129 { "12/03/99..12/04/01", "VALUE_RANGE 1 19991203 20011204" },
2130 { "03-12-99..04-14-01", "VALUE_RANGE 1 19990312 20010414" },
2131 { "01/30/60..02/02/59", "VALUE_RANGE 1 19600130 20590202" },
2132 { "1999-03-12..2001-04-14", "VALUE_RANGE 1 19990312 20010414" },
2133 { "12/03/99..02", "Unknown range operation" },
2134 { "1999-03-12..2001", "Unknown range operation" },
2135 { NULL, NULL }
2138 // Test DateRangeProcessor
2139 DEFINE_TESTCASE(qp_daterange1, !backend) {
2140 Xapian::QueryParser qp;
2141 Xapian::DateRangeProcessor rp_date(1, Xapian::RP_DATE_PREFER_MDY, 1960);
2142 qp.add_rangeprocessor(&rp_date);
2143 for (const test *p = test_value_daterange1_queries; p->query; ++p) {
2144 string expect, parsed;
2145 if (p->expect)
2146 expect = p->expect;
2147 else
2148 expect = "parse error";
2149 try {
2150 Xapian::Query qobj = qp.parse_query(p->query);
2151 parsed = qobj.get_description();
2152 expect = string("Query(") + expect + ')';
2153 } catch (const Xapian::QueryParserError &e) {
2154 parsed = e.get_msg();
2155 } catch (const Xapian::Error &e) {
2156 parsed = e.get_description();
2157 } catch (...) {
2158 parsed = "Unknown exception!";
2160 tout << "Query: " << p->query << '\n';
2161 TEST_STRINGS_EQUAL(parsed, expect);
2165 static const test test_value_daterange2_queries[] = {
2166 { "created:12/03/99..12/04/01", "VALUE_RANGE 1 19991203 20011204" },
2167 { "modified:03-12-99..04-14-01", "VALUE_RANGE 2 19990312 20010414" },
2168 { "accessed:01/30/70..02/02/69", "VALUE_RANGE 3 19700130 20690202" },
2169 // In <=1.2.12, and in 1.3.0, this gave "Unknown range operation":
2170 { "deleted:12/03/99..12/04/01", "VALUE_RANGE 4 19990312 20010412" },
2171 { "1999-03-12..2001-04-14", "Unknown range operation" },
2172 { "12/03/99..created:12/04/01", "Unknown range operation" },
2173 { "12/03/99created:..12/04/01", "Unknown range operation" },
2174 { "12/03/99..12/04/01created:", "Unknown range operation" },
2175 { "12/03/99..02", "Unknown range operation" },
2176 { "1999-03-12..2001", "Unknown range operation" },
2177 { NULL, NULL }
2180 // Feature test DateRangeProcessor with prefixes (added in 1.1.2).
2181 DEFINE_TESTCASE(qp_daterange2, !backend) {
2182 using Xapian::RP_DATE_PREFER_MDY;
2183 Xapian::QueryParser qp;
2184 Xapian::DateRangeProcessor rp_cdate(1, "created:", RP_DATE_PREFER_MDY, 1970);
2185 Xapian::DateRangeProcessor rp_mdate(2, "modified:", RP_DATE_PREFER_MDY, 1970);
2186 Xapian::DateRangeProcessor rp_adate(3, "accessed:", RP_DATE_PREFER_MDY, 1970);
2187 // Regression test - here a const char * was taken as a bool rather than a
2188 // std::string when resolving the overloaded forms. Fixed in 1.2.13 and
2189 // 1.3.1.
2190 Xapian::DateRangeProcessor rp_ddate(4, "deleted:");
2191 qp.add_rangeprocessor(&rp_cdate);
2192 qp.add_rangeprocessor(&rp_mdate);
2193 qp.add_rangeprocessor(&rp_adate);
2194 qp.add_rangeprocessor(&rp_ddate);
2195 for (const test *p = test_value_daterange2_queries; p->query; ++p) {
2196 string expect, parsed;
2197 if (p->expect)
2198 expect = p->expect;
2199 else
2200 expect = "parse error";
2201 try {
2202 Xapian::Query qobj = qp.parse_query(p->query);
2203 parsed = qobj.get_description();
2204 expect = string("Query(") + expect + ')';
2205 } catch (const Xapian::QueryParserError &e) {
2206 parsed = e.get_msg();
2207 } catch (const Xapian::Error &e) {
2208 parsed = e.get_description();
2209 } catch (...) {
2210 parsed = "Unknown exception!";
2212 tout << "Query: " << p->query << '\n';
2213 TEST_STRINGS_EQUAL(parsed, expect);
2217 static const test test_value_stringrange1_queries[] = {
2218 { "tag:bar..foo", "VALUE_RANGE 1 bar foo" },
2219 { "bar..foo", "VALUE_RANGE 0 bar foo" },
2220 { NULL, NULL }
2223 // Feature test RangeProcessor with prefixes.
2224 DEFINE_TESTCASE(qp_stringrange1, !backend) {
2225 Xapian::QueryParser qp;
2226 Xapian::RangeProcessor rp_default(0);
2227 Xapian::RangeProcessor rp_tag(1, "tag:");
2228 qp.add_rangeprocessor(&rp_tag);
2229 qp.add_rangeprocessor(&rp_default);
2230 for (const test *p = test_value_stringrange1_queries; p->query; ++p) {
2231 string expect, parsed;
2232 if (p->expect)
2233 expect = p->expect;
2234 else
2235 expect = "parse error";
2236 try {
2237 Xapian::Query qobj = qp.parse_query(p->query);
2238 parsed = qobj.get_description();
2239 expect = string("Query(") + expect + ')';
2240 } catch (const Xapian::QueryParserError &e) {
2241 parsed = e.get_msg();
2242 } catch (const Xapian::Error &e) {
2243 parsed = e.get_description();
2244 } catch (...) {
2245 parsed = "Unknown exception!";
2247 tout << "Query: " << p->query << '\n';
2248 TEST_STRINGS_EQUAL(parsed, expect);
2252 static const test test_value_customrange1_queries[] = {
2253 { "mars author:Asimov..Bradbury", "(mars@1 FILTER VALUE_RANGE 4 asimov bradbury)" },
2254 { NULL, NULL }
2257 struct AuthorRangeProcessor : public Xapian::RangeProcessor {
2258 AuthorRangeProcessor() : Xapian::RangeProcessor(4, "author:") { }
2260 Xapian::Query operator()(const std::string& b, const std::string& e)
2262 string begin = Xapian::Unicode::tolower(b);
2263 string end = Xapian::Unicode::tolower(e);
2264 return Xapian::RangeProcessor::operator()(begin, end);
2268 // Test custom RangeProcessor subclass.
2269 DEFINE_TESTCASE(qp_customrange1, !backend) {
2270 Xapian::QueryParser qp;
2271 AuthorRangeProcessor rp_author;
2272 qp.add_rangeprocessor(&rp_author);
2273 for (const test *p = test_value_customrange1_queries; p->query; ++p) {
2274 string expect, parsed;
2275 if (p->expect)
2276 expect = p->expect;
2277 else
2278 expect = "parse error";
2279 try {
2280 Xapian::Query qobj = qp.parse_query(p->query);
2281 parsed = qobj.get_description();
2282 expect = string("Query(") + expect + ')';
2283 } catch (const Xapian::QueryParserError &e) {
2284 parsed = e.get_msg();
2285 } catch (const Xapian::Error &e) {
2286 parsed = e.get_description();
2287 } catch (...) {
2288 parsed = "Unknown exception!";
2290 tout << "Query: " << p->query << '\n';
2291 TEST_STRINGS_EQUAL(parsed, expect);
2295 class TitleFieldProcessor : public Xapian::FieldProcessor {
2296 Xapian::Query operator()(const std::string & str) {
2297 if (str == "all")
2298 return Xapian::Query::MatchAll;
2299 return Xapian::Query("S" + str);
2303 class HostFieldProcessor : public Xapian::FieldProcessor {
2304 Xapian::Query operator()(const std::string & str) {
2305 if (str == "*")
2306 return Xapian::Query::MatchAll;
2307 string res = "H";
2308 for (string::const_iterator i = str.begin(); i != str.end(); ++i)
2309 res += C_tolower(*i);
2310 return Xapian::Query(res);
2314 static const test test_fieldproc1_queries[] = {
2315 { "title:test", "Stest" },
2316 { "title:all", "<alldocuments>" },
2317 { "host:Xapian.org", "0 * Hxapian.org" },
2318 { "host:*", "0 * <alldocuments>" },
2319 { "host:\"Space Station.Example.Org\"", "0 * Hspace station.example.org" },
2320 { NULL, NULL }
2323 // FieldProcessor test.
2324 DEFINE_TESTCASE(qp_fieldproc1, !backend) {
2325 Xapian::QueryParser qp;
2326 TitleFieldProcessor title_fproc;
2327 HostFieldProcessor host_fproc;
2328 qp.add_prefix("title", &title_fproc);
2329 qp.add_boolean_prefix("host", &host_fproc);
2330 for (const test *p = test_fieldproc1_queries; p->query; ++p) {
2331 string expect, parsed;
2332 if (p->expect)
2333 expect = p->expect;
2334 else
2335 expect = "parse error";
2336 try {
2337 Xapian::Query qobj = qp.parse_query(p->query);
2338 parsed = qobj.get_description();
2339 expect = string("Query(") + expect + ')';
2340 } catch (const Xapian::QueryParserError &e) {
2341 parsed = e.get_msg();
2342 } catch (const Xapian::Error &e) {
2343 parsed = e.get_description();
2344 } catch (...) {
2345 parsed = "Unknown exception!";
2347 tout << "Query: " << p->query << '\n';
2348 TEST_STRINGS_EQUAL(parsed, expect);
2352 class DateRangeFieldProcessor : public Xapian::FieldProcessor {
2353 Xapian::Query operator()(const std::string & str) {
2354 // In reality, these would be built from the current date, but for
2355 // testing it is much simpler to fix the date.
2356 if (str == "today")
2357 return Xapian::Query(Xapian::Query::OP_VALUE_GE, 1, "20120725");
2358 if (str == "this week")
2359 return Xapian::Query(Xapian::Query::OP_VALUE_GE, 1, "20120723");
2360 if (str == "this month")
2361 return Xapian::Query(Xapian::Query::OP_VALUE_GE, 1, "20120701");
2362 if (str == "this year")
2363 return Xapian::Query(Xapian::Query::OP_VALUE_GE, 1, "20120101");
2364 if (str == "this decade")
2365 return Xapian::Query(Xapian::Query::OP_VALUE_GE, 1, "20100101");
2366 if (str == "this century")
2367 return Xapian::Query(Xapian::Query::OP_VALUE_GE, 1, "20000101");
2368 throw Xapian::QueryParserError("Didn't understand date specification '" + str + "'");
2372 static const test test_fieldproc2_queries[] = {
2373 { "date:\"this week\"", "VALUE_GE 1 20120723" },
2374 { "date:23/7/2012..25/7/2012", "VALUE_RANGE 1 20120723 20120725" },
2375 { NULL, NULL }
2378 // Test using FieldProcessor and RangeProcessor together.
2379 DEFINE_TESTCASE(qp_fieldproc3, !backend) {
2380 Xapian::QueryParser qp;
2381 DateRangeFieldProcessor date_fproc;
2382 qp.add_boolean_prefix("date", &date_fproc);
2383 Xapian::DateRangeProcessor rp_date(1, "date:");
2384 qp.add_rangeprocessor(&rp_date);
2385 for (const test *p = test_fieldproc2_queries; p->query; ++p) {
2386 string expect, parsed;
2387 if (p->expect)
2388 expect = p->expect;
2389 else
2390 expect = "parse error";
2391 try {
2392 Xapian::Query qobj = qp.parse_query(p->query);
2393 parsed = qobj.get_description();
2394 expect = string("Query(") + expect + ')';
2395 } catch (const Xapian::QueryParserError &e) {
2396 parsed = e.get_msg();
2397 } catch (const Xapian::Error &e) {
2398 parsed = e.get_description();
2399 } catch (...) {
2400 parsed = "Unknown exception!";
2402 tout << "Query: " << p->query << '\n';
2403 TEST_STRINGS_EQUAL(parsed, expect);
2407 DEFINE_TESTCASE(qp_stoplist1, !backend) {
2408 Xapian::QueryParser qp;
2409 static const char * const stopwords[] = { "a", "an", "the" };
2410 Xapian::SimpleStopper stop(stopwords, stopwords + 3);
2411 qp.set_stopper(&stop);
2413 Xapian::TermIterator i;
2415 Xapian::Query query1 = qp.parse_query("some mice");
2416 i = qp.stoplist_begin();
2417 TEST(i == qp.stoplist_end());
2419 Xapian::Query query2 = qp.parse_query("the cat");
2420 i = qp.stoplist_begin();
2421 TEST(i != qp.stoplist_end());
2422 TEST_EQUAL(*i, "the");
2423 ++i;
2424 TEST(i == qp.stoplist_end());
2426 // Regression test - prior to Xapian 1.0.0 the stoplist wasn't being cleared
2427 // when a new query was parsed.
2428 Xapian::Query query3 = qp.parse_query("an aardvark");
2429 i = qp.stoplist_begin();
2430 TEST(i != qp.stoplist_end());
2431 TEST_EQUAL(*i, "an");
2432 ++i;
2433 TEST(i == qp.stoplist_end());
2436 static const test test_mispelled_queries[] = {
2437 { "doucment search", "document search" },
2438 { "doucment seeacrh", "document search" },
2439 { "docment seeacrh test", "document search test" },
2440 { "\"paragahp pineapple\"", "\"paragraph pineapple\"" },
2441 { "\"paragahp pineapple\"", "\"paragraph pineapple\"" },
2442 { "test S.E.A.R.C.", "" },
2443 { "this AND that", "" },
2444 { "documento", "document" },
2445 { "documento-documento", "document-document" },
2446 { "documento-searcho", "document-search" },
2447 { "test saerch", "test search" },
2448 { "paragraf search", "paragraph search" },
2449 { NULL, NULL }
2452 // Test spelling correction in the QueryParser.
2453 DEFINE_TESTCASE(qp_spell1, spelling) {
2454 Xapian::Database db = get_database("qp_spell1",
2455 [](Xapian::WritableDatabase& wdb,
2456 const string&) {
2457 Xapian::Document doc;
2458 doc.add_term("document", 6);
2459 doc.add_term("search", 7);
2460 doc.add_term("saerch", 1);
2461 doc.add_term("paragraph", 8);
2462 doc.add_term("paragraf", 2);
2463 wdb.add_document(doc);
2465 wdb.add_spelling("document");
2466 wdb.add_spelling("search");
2467 wdb.add_spelling("paragraph");
2468 wdb.add_spelling("band");
2471 Xapian::QueryParser qp;
2472 qp.set_stemming_strategy(Xapian::QueryParser::STEM_SOME);
2473 qp.set_database(db);
2475 for (const test *p = test_mispelled_queries; p->query; ++p) {
2476 Xapian::Query q;
2477 q = qp.parse_query(p->query,
2478 Xapian::QueryParser::FLAG_SPELLING_CORRECTION |
2479 Xapian::QueryParser::FLAG_BOOLEAN);
2480 tout << "Query: " << p->query << '\n';
2481 TEST_STRINGS_EQUAL(qp.get_corrected_query_string(), p->expect);
2485 // Test spelling correction in the QueryParser with multiple databases.
2486 DEFINE_TESTCASE(qp_spell2, spelling)
2488 Xapian::Database db1 = get_database("qp_spell2a",
2489 [](Xapian::WritableDatabase& wdb,
2490 const string&) {
2491 wdb.add_spelling("document");
2492 wdb.add_spelling("search");
2494 Xapian::Database db2 = get_database("qp_spell2b",
2495 [](Xapian::WritableDatabase& wdb,
2496 const string&) {
2497 wdb.add_spelling("document");
2498 wdb.add_spelling("paragraph");
2500 Xapian::Database db;
2501 db.add_database(db1);
2502 db.add_database(db2);
2504 Xapian::QueryParser qp;
2505 qp.set_stemming_strategy(Xapian::QueryParser::STEM_SOME);
2506 qp.set_database(db);
2508 for (const test *p = test_mispelled_queries; p->query; ++p) {
2509 Xapian::Query q;
2510 q = qp.parse_query(p->query,
2511 Xapian::QueryParser::FLAG_SPELLING_CORRECTION |
2512 Xapian::QueryParser::FLAG_BOOLEAN);
2513 tout << "Query: " << p->query << '\n';
2514 TEST_STRINGS_EQUAL(qp.get_corrected_query_string(), p->expect);
2518 static void
2519 gen_simple_spelling_db(Xapian::WritableDatabase& db, const string&)
2521 db.add_spelling("document");
2522 db.add_spelling("search");
2523 db.add_spelling("paragraph");
2524 db.add_spelling("band");
2527 static const test test_mispelled_wildcard_queries[] = {
2528 { "doucment", "document" },
2529 { "doucment*", "" },
2530 { "doucment* seearch", "doucment* search" },
2531 { "doucment* search", "" },
2532 { NULL, NULL }
2535 // Test spelling correction in the QueryParser with wildcards.
2536 // Regression test for bug fixed in 1.1.3 and 1.0.17.
2537 DEFINE_TESTCASE(qp_spellwild1, spelling) {
2538 Xapian::Database db = get_database("simple_spelling_db",
2539 gen_simple_spelling_db);
2540 Xapian::QueryParser qp;
2541 qp.set_database(db);
2543 const test *p;
2544 for (p = test_mispelled_queries; p->query; ++p) {
2545 Xapian::Query q;
2546 q = qp.parse_query(p->query,
2547 Xapian::QueryParser::FLAG_SPELLING_CORRECTION |
2548 Xapian::QueryParser::FLAG_BOOLEAN |
2549 Xapian::QueryParser::FLAG_WILDCARD);
2550 tout << "Query: " << p->query << '\n';
2551 TEST_STRINGS_EQUAL(qp.get_corrected_query_string(), p->expect);
2553 for (p = test_mispelled_wildcard_queries; p->query; ++p) {
2554 Xapian::Query q;
2555 q = qp.parse_query(p->query,
2556 Xapian::QueryParser::FLAG_SPELLING_CORRECTION |
2557 Xapian::QueryParser::FLAG_BOOLEAN |
2558 Xapian::QueryParser::FLAG_WILDCARD);
2559 tout << "Query: " << p->query << '\n';
2560 TEST_STRINGS_EQUAL(qp.get_corrected_query_string(), p->expect);
2564 static const test test_mispelled_partial_queries[] = {
2565 { "doucment", "" },
2566 { "doucment ", "document " },
2567 { "documen", "" },
2568 { "documen ", "document " },
2569 { "seearch documen", "search documen" },
2570 { "search documen", "" },
2571 { NULL, NULL }
2574 // Test spelling correction in the QueryParser with FLAG_PARTIAL.
2575 // Regression test for bug fixed in 1.1.3 and 1.0.17.
2576 DEFINE_TESTCASE(qp_spellpartial1, spelling) {
2577 Xapian::Database db = get_database("simple_spelling_db",
2578 gen_simple_spelling_db);
2579 Xapian::QueryParser qp;
2580 qp.set_database(db);
2582 for (const test *p = test_mispelled_partial_queries; p->query; ++p) {
2583 Xapian::Query q;
2584 q = qp.parse_query(p->query,
2585 Xapian::QueryParser::FLAG_SPELLING_CORRECTION |
2586 Xapian::QueryParser::FLAG_PARTIAL);
2587 tout << "Query: " << p->query << '\n';
2588 TEST_STRINGS_EQUAL(qp.get_corrected_query_string(), p->expect);
2592 static const test test_synonym_queries[] = {
2593 { "searching", "(Zsearch@1 SYNONYM Zfind@1 SYNONYM Zlocate@1)" },
2594 { "search", "(Zsearch@1 SYNONYM find@1)" },
2595 { "Search", "(search@1 SYNONYM find@1)" },
2596 { "Searching", "searching@1" },
2597 { "searching OR terms", "((Zsearch@1 SYNONYM Zfind@1 SYNONYM Zlocate@1) OR Zterm@2)" },
2598 { "search OR terms", "((Zsearch@1 SYNONYM find@1) OR Zterm@2)" },
2599 { "search +terms", "(Zterm@2 AND_MAYBE (Zsearch@1 SYNONYM find@1))" },
2600 { "search -terms", "((Zsearch@1 SYNONYM find@1) AND_NOT Zterm@2)" },
2601 { "+search terms", "((Zsearch@1 SYNONYM find@1) AND_MAYBE Zterm@2)" },
2602 { "-search terms", "(Zterm@2 AND_NOT (Zsearch@1 SYNONYM find@1))" },
2603 { "search terms", "((Zsearch@1 SYNONYM find@1) OR Zterm@2)" },
2604 // Shouldn't trigger synonyms:
2605 { "\"search terms\"", "(search@1 PHRASE 2 terms@2)" },
2606 // Check that setting FLAG_AUTO_SYNONYMS doesn't enable multi-word
2607 // synonyms. Regression test for bug fixed in 1.3.0 and 1.2.9.
2608 { "regression test", "(Zregress@1 OR Ztest@2)" },
2609 { NULL, NULL }
2612 // Test single term synonyms in the QueryParser.
2613 DEFINE_TESTCASE(qp_synonym1, synonyms) {
2614 Xapian::Database db = get_database("qp_synonym1",
2615 [](Xapian::WritableDatabase& wdb,
2616 const string&) {
2617 wdb.add_synonym("Zsearch", "Zfind");
2618 wdb.add_synonym("Zsearch",
2619 "Zlocate");
2620 wdb.add_synonym("search", "find");
2621 wdb.add_synonym("Zseek", "Zsearch");
2622 wdb.add_synonym("regression test",
2623 "magic");
2626 Xapian::QueryParser qp;
2627 qp.set_stemmer(Xapian::Stem("english"));
2628 qp.set_stemming_strategy(Xapian::QueryParser::STEM_SOME);
2629 qp.set_database(db);
2631 for (const test *p = test_synonym_queries; p->query; ++p) {
2632 string expect = "Query(";
2633 expect += p->expect;
2634 expect += ')';
2635 Xapian::Query q;
2636 q = qp.parse_query(p->query, qp.FLAG_AUTO_SYNONYMS|qp.FLAG_DEFAULT);
2637 tout << "Query: " << p->query << '\n';
2638 TEST_STRINGS_EQUAL(q.get_description(), expect);
2642 static const test test_multi_synonym_queries[] = {
2643 { "sun OR tan OR cream", "(Zsun@1 OR Ztan@2 OR Zcream@3)" },
2644 { "sun tan", "((Zsun@1 OR Ztan@2) SYNONYM bathe@1)" },
2645 { "sun tan cream", "((Zsun@1 OR Ztan@2 OR Zcream@3) SYNONYM lotion@1)" },
2646 { "beach sun tan holiday", "(Zbeach@1 OR ((Zsun@2 OR Ztan@3) SYNONYM bathe@2) OR Zholiday@4)" },
2647 { "sun tan sun tan cream", "(((Zsun@1 OR Ztan@2) SYNONYM bathe@1) OR ((Zsun@3 OR Ztan@4 OR Zcream@5) SYNONYM lotion@3))" },
2648 { "single", "(Zsingl@1 SYNONYM record@1)" },
2649 { NULL, NULL }
2652 // Test multi term synonyms in the QueryParser.
2653 DEFINE_TESTCASE(qp_synonym2, synonyms) {
2654 Xapian::Database db = get_database("qp_synonym2",
2655 [](Xapian::WritableDatabase& wdb,
2656 const string&) {
2657 wdb.add_synonym("sun tan cream",
2658 "lotion");
2659 wdb.add_synonym("sun tan", "bathe");
2660 wdb.add_synonym("single", "record");
2663 Xapian::QueryParser qp;
2664 qp.set_stemmer(Xapian::Stem("english"));
2665 qp.set_stemming_strategy(Xapian::QueryParser::STEM_SOME);
2666 qp.set_database(db);
2668 for (const test *p = test_multi_synonym_queries; p->query; ++p) {
2669 string expect = "Query(";
2670 expect += p->expect;
2671 expect += ')';
2672 Xapian::Query q;
2673 q = qp.parse_query(p->query,
2674 Xapian::QueryParser::FLAG_AUTO_MULTIWORD_SYNONYMS |
2675 Xapian::QueryParser::FLAG_DEFAULT);
2676 tout << "Query: " << p->query << '\n';
2677 TEST_STRINGS_EQUAL(q.get_description(), expect);
2681 static const test test_synonym_op_queries[] = {
2682 { "searching", "Zsearch@1" },
2683 { "~searching", "(Zsearch@1 SYNONYM Zfind@1 SYNONYM Zlocate@1)" },
2684 { "~search", "(Zsearch@1 SYNONYM find@1)" },
2685 { "~Search", "(search@1 SYNONYM find@1)" },
2686 { "~Searching", "searching@1" },
2687 { "~searching OR terms", "((Zsearch@1 SYNONYM Zfind@1 SYNONYM Zlocate@1) OR Zterm@2)" },
2688 { "~search OR terms", "((Zsearch@1 SYNONYM find@1) OR Zterm@2)" },
2689 { "~search +terms", "(Zterm@2 AND_MAYBE (Zsearch@1 SYNONYM find@1))" },
2690 { "~search -terms", "((Zsearch@1 SYNONYM find@1) AND_NOT Zterm@2)" },
2691 { "+~search terms", "((Zsearch@1 SYNONYM find@1) AND_MAYBE Zterm@2)" },
2692 { "-~search terms", "(Zterm@2 AND_NOT (Zsearch@1 SYNONYM find@1))" },
2693 { "~search terms", "((Zsearch@1 SYNONYM find@1) OR Zterm@2)" },
2694 { "~foo:search", "(ZXFOOsearch@1 SYNONYM prefixated@1)" },
2695 // FIXME: should look for multi-term synonym...
2696 { "~\"search terms\"", "(search@1 PHRASE 2 terms@2)" },
2697 { NULL, NULL }
2700 // Test the synonym operator in the QueryParser.
2701 DEFINE_TESTCASE(qp_synonym3, synonyms) {
2702 Xapian::Database db = get_database("qp_synonym3",
2703 [](Xapian::WritableDatabase& wdb,
2704 const string&) {
2705 wdb.add_synonym("Zsearch", "Zfind");
2706 wdb.add_synonym("Zsearch",
2707 "Zlocate");
2708 wdb.add_synonym("search", "find");
2709 wdb.add_synonym("Zseek", "Zsearch");
2710 wdb.add_synonym("ZXFOOsearch",
2711 "prefixated");
2714 Xapian::QueryParser qp;
2715 qp.set_stemmer(Xapian::Stem("english"));
2716 qp.set_stemming_strategy(Xapian::QueryParser::STEM_SOME);
2717 qp.set_database(db);
2718 qp.add_prefix("foo", "XFOO");
2720 for (const test *p = test_synonym_op_queries; p->query; ++p) {
2721 string expect = "Query(";
2722 expect += p->expect;
2723 expect += ')';
2724 Xapian::Query q;
2725 q = qp.parse_query(p->query,
2726 Xapian::QueryParser::FLAG_SYNONYM |
2727 Xapian::QueryParser::FLAG_BOOLEAN |
2728 Xapian::QueryParser::FLAG_LOVEHATE |
2729 Xapian::QueryParser::FLAG_PHRASE);
2730 tout << "Query: " << p->query << '\n';
2731 TEST_STRINGS_EQUAL(q.get_description(), expect);
2735 static const test test_stem_all_queries[] = {
2736 { "\"chemical engineers\"", "(chemic@1 PHRASE 2 engin@2)" },
2737 { "chemical NEAR engineers", "(chemic@1 NEAR 11 engin@2)" },
2738 { "chemical engineers", "(chemic@1 OR engin@2)" },
2739 { "title:(chemical engineers)", "(XTchemic@1 OR XTengin@2)" },
2740 { NULL, NULL }
2743 DEFINE_TESTCASE(qp_stem_all1, !backend) {
2744 Xapian::QueryParser qp;
2745 qp.set_stemmer(Xapian::Stem("english"));
2746 qp.set_stemming_strategy(qp.STEM_ALL);
2747 qp.add_prefix("title", "XT");
2748 for (const test *p = test_stem_all_queries; p->query; ++p) {
2749 string expect, parsed;
2750 if (p->expect)
2751 expect = p->expect;
2752 else
2753 expect = "parse error";
2754 try {
2755 Xapian::Query qobj = qp.parse_query(p->query);
2756 parsed = qobj.get_description();
2757 expect = string("Query(") + expect + ')';
2758 } catch (const Xapian::QueryParserError &e) {
2759 parsed = e.get_msg();
2760 } catch (const Xapian::Error &e) {
2761 parsed = e.get_description();
2762 } catch (...) {
2763 parsed = "Unknown exception!";
2765 tout << "Query: " << p->query << '\n';
2766 TEST_STRINGS_EQUAL(parsed, expect);
2770 static const test test_stem_all_z_queries[] = {
2771 { "\"chemical engineers\"", "(Zchemic@1 PHRASE 2 Zengin@2)" },
2772 { "chemical NEAR engineers", "(Zchemic@1 NEAR 11 Zengin@2)" },
2773 { "chemical engineers", "(Zchemic@1 OR Zengin@2)" },
2774 { "title:(chemical engineers)", "(ZXTchemic@1 OR ZXTengin@2)" },
2775 { NULL, NULL }
2778 DEFINE_TESTCASE(qp_stem_all_z1, !backend) {
2779 Xapian::QueryParser qp;
2780 qp.set_stemmer(Xapian::Stem("english"));
2781 qp.set_stemming_strategy(qp.STEM_ALL_Z);
2782 qp.add_prefix("title", "XT");
2783 for (const test *p = test_stem_all_z_queries; p->query; ++p) {
2784 string expect, parsed;
2785 if (p->expect)
2786 expect = p->expect;
2787 else
2788 expect = "parse error";
2789 try {
2790 Xapian::Query qobj = qp.parse_query(p->query);
2791 parsed = qobj.get_description();
2792 expect = string("Query(") + expect + ')';
2793 } catch (const Xapian::QueryParserError &e) {
2794 parsed = e.get_msg();
2795 } catch (const Xapian::Error &e) {
2796 parsed = e.get_description();
2797 } catch (...) {
2798 parsed = "Unknown exception!";
2800 tout << "Query: " << p->query << '\n';
2801 TEST_STRINGS_EQUAL(parsed, expect);
2805 static double
2806 time_query_parse(const Xapian::Database & db, const string & q,
2807 int repetitions, unsigned flags)
2809 Xapian::QueryParser qp;
2810 qp.set_database(db);
2811 CPUTimer timer;
2812 std::vector<Xapian::Query> qs;
2813 qs.reserve(repetitions);
2814 for (int i = 0; i != repetitions; ++i) {
2815 qs.push_back(qp.parse_query(q, flags));
2817 if (repetitions > 1) {
2818 Xapian::Query qc(Xapian::Query::OP_OR, qs.begin(), qs.end());
2820 return timer.get_time();
2823 static void
2824 qp_scale1_helper(const Xapian::Database &db, const string & q, unsigned n,
2825 unsigned flags)
2827 double time1;
2828 while (true) {
2829 time1 = time_query_parse(db, q, n, flags);
2830 if (time1 != 0.0) break;
2832 // The first test completed before the timer ticked at all, so increase
2833 // the number of repetitions and retry.
2834 unsigned n_new = n * 10;
2835 if (n_new < n)
2836 SKIP_TEST("Can't count enough repetitions to be able to time test");
2837 n = n_new;
2840 n /= 5;
2842 string q_n;
2843 q_n.reserve(q.size() * n);
2844 for (unsigned i = n; i != 0; --i) {
2845 q_n += q;
2848 // Time 5 repetitions so we average random variations a bit.
2849 double time2 = time_query_parse(db, q_n, 5, flags);
2850 tout << "small=" << time1 << "s, large=" << time2 << "s\n";
2852 // Allow a factor of 2.15 difference, to cover random variation and a
2853 // native time interval which isn't an exact multiple of 1/CLK_TCK.
2854 TEST_REL(time2,<,time1 * 2.15);
2857 // Regression test: check that query parser doesn't scale very badly with the
2858 // size of the query.
2859 DEFINE_TESTCASE(qp_scale1, writable && synonyms) {
2860 Xapian::WritableDatabase db = get_writable_database();
2862 db.add_synonym("foo", "bar");
2863 db.commit();
2865 string q1("foo ");
2866 string q1b("baz ");
2867 const unsigned repetitions = 5000;
2869 // A long multiword synonym.
2870 string syn;
2871 for (int j = 50; j != 0; --j) {
2872 syn += q1;
2874 syn.resize(syn.size() - 1);
2876 unsigned synflags = Xapian::QueryParser::FLAG_DEFAULT |
2877 Xapian::QueryParser::FLAG_SYNONYM |
2878 Xapian::QueryParser::FLAG_AUTO_MULTIWORD_SYNONYMS;
2880 // First, we test a simple query.
2881 qp_scale1_helper(db, q1, repetitions, Xapian::QueryParser::FLAG_DEFAULT);
2883 // If synonyms are enabled, a different code-path is followed.
2884 // Test a query which has no synonyms.
2885 qp_scale1_helper(db, q1b, repetitions, synflags);
2887 // Test a query which has short synonyms.
2888 qp_scale1_helper(db, q1, repetitions, synflags);
2890 // Add a synonym for the whole query, to test that code path.
2891 db.add_synonym(syn, "bar");
2892 db.commit();
2894 qp_scale1_helper(db, q1, repetitions, synflags);
2897 static const test test_near_queries[] = {
2898 { "simple-example", "(simple@1 PHRASE 2 example@2)" },
2899 { "stock -cooking", "(Zstock@1 AND_NOT Zcook@2)" },
2900 // FIXME: these give NEAR 2
2901 // { "foo -baz bar", "((foo@1 NEAR 11 bar@3) AND_NOT Zbaz@2)" },
2902 // { "one +two three", "(Ztwo@2 AND_MAYBE (one@1 NEAR 11 three@3))" },
2903 { "foo bar", "(foo@1 NEAR 11 bar@2)" },
2904 { "foo bar baz", "(foo@1 NEAR 12 bar@2 NEAR 12 baz@3)" },
2905 { "gtk+ -gnome", "(Zgtk+@1 AND_NOT Zgnome@2)" },
2906 { "c++ -d--", "(Zc++@1 AND_NOT Zd@2)" },
2907 { "\"c++ library\"", "(c++@1 PHRASE 2 library@2)" },
2908 { "author:orwell animal farm", "(Aorwell@1 NEAR 12 animal@2 NEAR 12 farm@3)" },
2909 { "author:Orwell Animal Farm", "(Aorwell@1 NEAR 12 animal@2 NEAR 12 farm@3)" },
2910 { "beer NOT \"orange juice\"", "(Zbeer@1 AND_NOT (orange@2 PHRASE 2 juice@3))" },
2911 { "beer AND NOT lager", "(Zbeer@1 AND_NOT Zlager@2)" },
2912 { "A OR B NOT C", "(a@1 OR (b@2 AND_NOT c@3))" },
2913 { "A OR B AND NOT C", "(a@1 OR (b@2 AND_NOT c@3))" },
2914 { "A OR B XOR C", "(a@1 OR (b@2 XOR c@3))" },
2915 { "A XOR B NOT C", "(a@1 XOR (b@2 AND_NOT c@3))" },
2916 { "one AND two", "(Zone@1 AND Ztwo@2)" },
2917 { "NOT windows", "Syntax: <expression> NOT <expression>" },
2918 { "a AND (NOT b)", "Syntax: <expression> NOT <expression>" },
2919 { "AND NOT windows", "Syntax: <expression> AND NOT <expression>" },
2920 { "gordian NOT", "Syntax: <expression> NOT <expression>" },
2921 { "gordian AND NOT", "Syntax: <expression> AND NOT <expression>" },
2922 { "foo OR (something AND)", "Syntax: <expression> AND <expression>" },
2923 { "OR foo", "Syntax: <expression> OR <expression>" },
2924 { "XOR", "Syntax: <expression> XOR <expression>" },
2925 { "hard\xa0space", "(hard@1 NEAR 11 space@2)" },
2926 { NULL, NULL }
2929 DEFINE_TESTCASE(qp_near1, !backend) {
2930 Xapian::QueryParser queryparser;
2931 queryparser.set_stemmer(Xapian::Stem("english"));
2932 queryparser.set_stemming_strategy(Xapian::QueryParser::STEM_SOME);
2933 queryparser.add_prefix("author", "A");
2934 queryparser.add_prefix("writer", "A");
2935 queryparser.add_prefix("title", "XT");
2936 queryparser.add_prefix("subject", "XT");
2937 queryparser.add_prefix("authortitle", "A");
2938 queryparser.add_prefix("authortitle", "XT");
2939 queryparser.add_boolean_prefix("site", "H");
2940 queryparser.add_boolean_prefix("site2", "J");
2941 queryparser.add_boolean_prefix("multisite", "H");
2942 queryparser.add_boolean_prefix("multisite", "J");
2943 queryparser.add_boolean_prefix("category", "XCAT", false);
2944 queryparser.add_boolean_prefix("dogegory", "XDOG", false);
2945 queryparser.set_default_op(Xapian::Query::OP_NEAR);
2946 for (const test *p = test_near_queries; p->query; ++p) {
2947 string expect, parsed;
2948 if (p->expect)
2949 expect = p->expect;
2950 else
2951 expect = "parse error";
2952 try {
2953 Xapian::Query qobj = queryparser.parse_query(p->query);
2954 parsed = qobj.get_description();
2955 expect = string("Query(") + expect + ')';
2956 } catch (const Xapian::QueryParserError &e) {
2957 parsed = e.get_msg();
2958 } catch (const Xapian::Error &e) {
2959 parsed = e.get_description();
2960 } catch (...) {
2961 parsed = "Unknown exception!";
2963 tout << "Query: " << p->query << '\n';
2964 TEST_STRINGS_EQUAL(parsed, expect);
2968 static const test test_phrase_queries[] = {
2969 { "simple-example", "(simple@1 PHRASE 2 example@2)" },
2970 { "stock -cooking", "(Zstock@1 AND_NOT Zcook@2)" },
2971 // FIXME: these give PHRASE 2
2972 // { "foo -baz bar", "((foo@1 PHRASE 11 bar@3) AND_NOT Zbaz@2)" },
2973 // { "one +two three", "(Ztwo@2 AND_MAYBE (one@1 PHRASE 11 three@3))" },
2974 { "foo bar", "(foo@1 PHRASE 11 bar@2)" },
2975 { "foo bar baz", "(foo@1 PHRASE 12 bar@2 PHRASE 12 baz@3)" },
2976 { "gtk+ -gnome", "(Zgtk+@1 AND_NOT Zgnome@2)" },
2977 { "c++ -d--", "(Zc++@1 AND_NOT Zd@2)" },
2978 { "\"c++ library\"", "(c++@1 PHRASE 2 library@2)" },
2979 { "author:orwell animal farm", "(Aorwell@1 PHRASE 12 animal@2 PHRASE 12 farm@3)" },
2980 { "author:Orwell Animal Farm", "(Aorwell@1 PHRASE 12 animal@2 PHRASE 12 farm@3)" },
2981 { "beer NOT \"orange juice\"", "(Zbeer@1 AND_NOT (orange@2 PHRASE 2 juice@3))" },
2982 { "beer AND NOT lager", "(Zbeer@1 AND_NOT Zlager@2)" },
2983 { "A OR B NOT C", "(a@1 OR (b@2 AND_NOT c@3))" },
2984 { "A OR B AND NOT C", "(a@1 OR (b@2 AND_NOT c@3))" },
2985 { "A OR B XOR C", "(a@1 OR (b@2 XOR c@3))" },
2986 { "A XOR B NOT C", "(a@1 XOR (b@2 AND_NOT c@3))" },
2987 { "one AND two", "(Zone@1 AND Ztwo@2)" },
2988 { "NOT windows", "Syntax: <expression> NOT <expression>" },
2989 { "a AND (NOT b)", "Syntax: <expression> NOT <expression>" },
2990 { "AND NOT windows", "Syntax: <expression> AND NOT <expression>" },
2991 { "gordian NOT", "Syntax: <expression> NOT <expression>" },
2992 { "gordian AND NOT", "Syntax: <expression> AND NOT <expression>" },
2993 { "foo OR (something AND)", "Syntax: <expression> AND <expression>" },
2994 { "OR foo", "Syntax: <expression> OR <expression>" },
2995 { "XOR", "Syntax: <expression> XOR <expression>" },
2996 { "hard\xa0space", "(hard@1 PHRASE 11 space@2)" },
2997 // FIXME: this isn't what we want, but fixing phrase to work with
2998 // subqueries first might be the best approach.
2999 // FIXME: this isn't currently reimplemented:
3000 // { "(one AND two) three", "((Zone@1 PHRASE 11 Zthree@3) AND (Ztwo@2 PHRASE 11 Zthree@3))" },
3001 { NULL, NULL }
3004 DEFINE_TESTCASE(qp_phrase1, !backend) {
3005 Xapian::QueryParser queryparser;
3006 queryparser.set_stemmer(Xapian::Stem("english"));
3007 queryparser.set_stemming_strategy(Xapian::QueryParser::STEM_SOME);
3008 queryparser.add_prefix("author", "A");
3009 queryparser.add_prefix("writer", "A");
3010 queryparser.add_prefix("title", "XT");
3011 queryparser.add_prefix("subject", "XT");
3012 queryparser.add_prefix("authortitle", "A");
3013 queryparser.add_prefix("authortitle", "XT");
3014 queryparser.add_boolean_prefix("site", "H");
3015 queryparser.add_boolean_prefix("site2", "J");
3016 queryparser.add_boolean_prefix("multisite", "H");
3017 queryparser.add_boolean_prefix("multisite", "J");
3018 queryparser.add_boolean_prefix("category", "XCAT", false);
3019 queryparser.add_boolean_prefix("dogegory", "XDOG", false);
3020 queryparser.set_default_op(Xapian::Query::OP_PHRASE);
3021 for (const test *p = test_phrase_queries; p->query; ++p) {
3022 string expect, parsed;
3023 if (p->expect)
3024 expect = p->expect;
3025 else
3026 expect = "parse error";
3027 try {
3028 Xapian::Query qobj = queryparser.parse_query(p->query);
3029 parsed = qobj.get_description();
3030 expect = string("Query(") + expect + ')';
3031 } catch (const Xapian::QueryParserError &e) {
3032 parsed = e.get_msg();
3033 } catch (const Xapian::Error &e) {
3034 parsed = e.get_description();
3035 } catch (...) {
3036 parsed = "Unknown exception!";
3038 tout << "Query: " << p->query << '\n';
3039 TEST_STRINGS_EQUAL(parsed, expect);
3043 static const test test_stopword_group_or_queries[] = {
3044 { "this is a test", "test@4" },
3045 { "test*", "WILDCARD SYNONYM test" },
3046 { "a test*", "WILDCARD SYNONYM test" },
3047 { "is a test*", "WILDCARD SYNONYM test" },
3048 { "this is a test*", "WILDCARD SYNONYM test" },
3049 { "this is a us* test*", "(WILDCARD SYNONYM us OR WILDCARD SYNONYM test)" },
3050 { "this is a user test*", "(user@4 OR WILDCARD SYNONYM test)" },
3051 { NULL, NULL }
3054 static const test test_stopword_group_and_queries[] = {
3055 { "this is a test", "test@4" },
3056 { "test*", "WILDCARD SYNONYM test" },
3057 { "a test*", "WILDCARD SYNONYM test" },
3058 // Two stopwords + one wildcard failed in 1.0.16
3059 { "is a test*", "WILDCARD SYNONYM test" },
3060 // Three stopwords + one wildcard failed in 1.0.16
3061 { "this is a test*", "WILDCARD SYNONYM test" },
3062 // Three stopwords + two wildcards failed in 1.0.16
3063 { "this is a us* test*", "(WILDCARD SYNONYM us AND WILDCARD SYNONYM test)" },
3064 { "this is a user test*", "(user@4 AND WILDCARD SYNONYM test)" },
3065 { NULL, NULL }
3068 // Regression test for bug fixed in 1.0.17 and 1.1.3.
3069 DEFINE_TESTCASE(qp_stopword_group1, backend) {
3070 Xapian::Database db = get_database("qp_stopword_group1",
3071 [](Xapian::WritableDatabase& wdb,
3072 const string&) {
3073 Xapian::Document doc;
3074 doc.add_term("test");
3075 doc.add_term("tester");
3076 doc.add_term("testable");
3077 doc.add_term("user");
3078 wdb.add_document(doc);
3081 Xapian::SimpleStopper stopper;
3082 stopper.add("this");
3083 stopper.add("is");
3084 stopper.add("a");
3086 Xapian::QueryParser qp;
3087 qp.set_stopper(&stopper);
3088 qp.set_database(db);
3090 // Process test cases with OP_OR first, then with OP_AND.
3091 qp.set_default_op(Xapian::Query::OP_OR);
3092 const test *p = test_stopword_group_or_queries;
3093 for (int i = 1; i <= 2; ++i) {
3094 for ( ; p->query; ++p) {
3095 string expect, parsed;
3096 if (p->expect)
3097 expect = p->expect;
3098 else
3099 expect = "parse error";
3100 try {
3101 Xapian::Query qobj = qp.parse_query(p->query, qp.FLAG_WILDCARD);
3102 parsed = qobj.get_description();
3103 expect = string("Query(") + expect + ')';
3104 } catch (const Xapian::QueryParserError &e) {
3105 parsed = e.get_msg();
3106 } catch (const Xapian::Error &e) {
3107 parsed = e.get_description();
3108 } catch (...) {
3109 parsed = "Unknown exception!";
3111 tout << "Query: " << p->query << '\n';
3112 TEST_STRINGS_EQUAL(parsed, expect);
3115 qp.set_default_op(Xapian::Query::OP_AND);
3116 p = test_stopword_group_and_queries;
3120 /// Check that QueryParser::set_default_op() rejects inappropriate ops.
3121 DEFINE_TESTCASE(qp_default_op2, !backend) {
3122 Xapian::QueryParser qp;
3123 static const Xapian::Query::op ops[] = {
3124 Xapian::Query::OP_AND_NOT,
3125 Xapian::Query::OP_XOR,
3126 Xapian::Query::OP_AND_MAYBE,
3127 Xapian::Query::OP_FILTER,
3128 Xapian::Query::OP_VALUE_RANGE,
3129 Xapian::Query::OP_SCALE_WEIGHT,
3130 Xapian::Query::OP_VALUE_GE,
3131 Xapian::Query::OP_VALUE_LE
3133 for (Xapian::Query::op op : ops) {
3134 tout << op << '\n';
3135 TEST_EXCEPTION(Xapian::InvalidArgumentError,
3136 qp.set_default_op(op));
3137 TEST_EQUAL(qp.get_default_op(), Xapian::Query::OP_OR);
3141 struct qp_default_op3_test {
3142 Xapian::Query::op op;
3143 const char *expect;
3146 /// Check that QueryParser::set_default_op() accepts appropriate ops.
3147 DEFINE_TESTCASE(qp_default_op3, !backend) {
3148 Xapian::QueryParser qp;
3149 static const qp_default_op3_test tests[] = {
3150 { Xapian::Query::OP_AND,
3151 "Query((a@1 AND b@2 AND c@3))" },
3152 { Xapian::Query::OP_OR,
3153 "Query((a@1 OR b@2 OR c@3))" },
3154 { Xapian::Query::OP_PHRASE,
3155 "Query((a@1 PHRASE 12 b@2 PHRASE 12 c@3))" },
3156 { Xapian::Query::OP_NEAR,
3157 "Query((a@1 NEAR 12 b@2 NEAR 12 c@3))" },
3158 { Xapian::Query::OP_ELITE_SET,
3159 "Query((a@1 ELITE_SET 10 b@2 ELITE_SET 10 c@3))" },
3160 { Xapian::Query::OP_SYNONYM,
3161 "Query((a@1 SYNONYM b@2 SYNONYM c@3))" },
3163 for (auto& test : tests) {
3164 tout << test.op << '\n';
3165 qp.set_default_op(test.op);
3166 // Check that get_default_op() returns what we just set.
3167 TEST_EQUAL(qp.get_default_op(), test.op);
3168 TEST_EQUAL(qp.parse_query("A B C").get_description(), test.expect);
3172 /// Test that the default strategy is now STEM_SOME (as of 1.3.1).
3173 DEFINE_TESTCASE(qp_defaultstrategysome1, !backend) {
3174 Xapian::QueryParser qp;
3175 qp.set_stemmer(Xapian::Stem("en"));
3176 TEST_EQUAL(qp.parse_query("testing").get_description(), "Query(Ztest@1)");
3179 /// Test STEM_SOME_FULL_POS.
3180 DEFINE_TESTCASE(qp_stemsomefullpos, !backend) {
3181 Xapian::QueryParser qp;
3182 qp.set_stemmer(Xapian::Stem("en"));
3183 qp.set_stemming_strategy(qp.STEM_SOME_FULL_POS);
3184 TEST_EQUAL(qp.parse_query("terms NEAR testing").get_description(), "Query((Zterm@1 NEAR 11 Ztest@2))");
3185 TEST_EQUAL(qp.parse_query("terms ADJ testing").get_description(), "Query((Zterm@1 PHRASE 11 Ztest@2))");
3188 /** Regression test for segfault on synonym query with no matches.
3190 * This bug was introduced and fixed in git master before 1.5.0.
3192 DEFINE_TESTCASE(qp_synonymcrash1, synonyms) {
3193 Xapian::Database db = get_database("qp_synonymcrash1",
3194 [](Xapian::WritableDatabase& wdb,
3195 const string &) {
3196 wdb.add_synonym("baa", "bar");
3198 Xapian::QueryParser qp;
3199 qp.set_database(db);
3200 auto q = qp.parse_query("~baa ~baa", qp.FLAG_SYNONYM);
3201 Xapian::Enquire enq(db);
3202 enq.set_query(q);
3203 Xapian::MSet results = enq.get_mset(0, 10);
3204 TEST_EQUAL(results.size(), 0);
3207 DEFINE_TESTCASE(qp_nopos, !backend) {
3208 static const test tests[] = {
3209 { "no pos anyway", "(no@1 OR pos@2 OR anyway@3)" },
3210 { "w ADJ x", "(w@1 AND x@2)" },
3211 { "\"phrase q\" OR A NEAR/4 B", "((phrase@1 AND q@2) OR (a@3 AND b@4))" },
3212 // Check FLAG_NO_POSITIONS stays on if we reparse with fewer flags.
3213 { "a-b NEAR x", "((a@1 AND b@2) OR (near@3 OR x@4))" },
3215 Xapian::QueryParser qp;
3216 const auto flags = qp.FLAG_DEFAULT | qp.FLAG_NO_POSITIONS;
3217 for (const test& p : tests) {
3218 string expect, parsed;
3219 if (p.expect)
3220 expect = p.expect;
3221 else
3222 expect = "parse error";
3223 try {
3224 Xapian::Query q = qp.parse_query(p.query, flags);
3225 parsed = q.get_description();
3226 expect = string("Query(") + expect + ')';
3227 } catch (const Xapian::QueryParserError& e) {
3228 parsed = e.get_msg();
3229 } catch (const Xapian::Error& e) {
3230 parsed = e.get_description();
3231 } catch (...) {
3232 parsed = "Unknown exception!";
3234 tout << "Query: " << p.query << '\n';
3235 TEST_STRINGS_EQUAL(parsed, expect);