xapian-core  1.4.31
api_queryparser.cc
Go to the documentation of this file.
1 
4 /* Copyright (C) 2002-2026 Olly Betts
5  * Copyright (C) 2006,2007,2009 Lemur Consulting Ltd
6  *
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.
11  *
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.
16  *
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
21  */
22 
23 #include <config.h>
24 
25 #include "api_queryparser.h"
26 
27 #define XAPIAN_DEPRECATED(D) D
28 #include <xapian.h>
29 
30 #include "apitest.h"
31 #include "cputimer.h"
32 #include "str.h"
33 #include "stringutils.h"
34 
35 #include <string>
36 #include <vector>
37 
38 using namespace std;
39 
40 #include "testsuite.h"
41 #include "testutils.h"
42 
43 struct test {
44  const char *query;
45  const char *expect;
46 };
47 
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  // Regression test - Unicode character values were truncated to 8 bits
174  // before testing C_isdigit(), so this rather artificial example parsed
175  // to: (a@1 NEAR 262 b@2)
176  { "a NEAR/\xc4\xb5 b", "(Za@1 OR (near@2 PHRASE 2 \xc4\xb5@3) OR Zb@4)" },
177  { "a ADJ/\xc4\xb5 b", "(Za@1 OR (adj@2 PHRASE 2 \xc4\xb5@3) OR Zb@4)" },
178  // Regression test - the first two cases were parsed as if the '/' were a
179  // space, which was inconsistent with the second two. Fixed in 1.2.5.
180  { "a NEAR/b", "(Za@1 OR (near@2 PHRASE 2 b@3))" },
181  { "a ADJ/b", "(Za@1 OR (adj@2 PHRASE 2 b@3))" },
182  { "a NEAR/b c", "(Za@1 OR (near@2 PHRASE 2 b@3) OR Zc@4)" },
183  { "a ADJ/b c", "(Za@1 OR (adj@2 PHRASE 2 b@3) OR Zc@4)" },
184  // Regression tests - + and - didn't work on bracketed subexpressions prior
185  // to 1.0.2.
186  { "+(one two) three", "((Zone@1 OR Ztwo@2) AND_MAYBE Zthree@3)" },
187  { "zero -(one two)", "(Zzero@1 AND_NOT (Zone@2 OR Ztwo@3))" },
188  // Feature tests that ':' is inserted between prefix and term correctly:
189  { "category:Foo", "0 * XCAT:Foo" },
190  { "category:foo", "0 * XCATfoo" },
191  { "category:\xc3\x96oo", "0 * XCAT\xc3\x96oo" },
192  { "category::colon", "0 * XCAT::colon" },
193  // Feature tests for quoted boolean terms:
194  { "category:\"Hello world\"", "0 * XCAT:Hello world" },
195  { "category:\"literal \"\"\"", "0 * XCATliteral \"" },
196  { "category:\" \"", "0 * XCAT " },
197  { "category:\"\"", "0 * XCAT" },
198  { "category:\"(unterminated)", "0 * XCAT(unterminated)" },
199  // Feature tests for curly double quotes:
200  { "“curly quotes”", "(curly@1 PHRASE 2 quotes@2)" },
201  // Test "" inside quoted phrase doesn't end the phrase (for consistency
202  // with "" being an escape " in a quoted boolean term.
203  { "subject:\"foo\"\"bar\"", "(XTfoo@1 PHRASE 2 XTbar@2)" },
204  // Feature tests for implicitly closing brackets:
205  { "(foo", "Zfoo@1" },
206  { "(foo XOR bar", "(Zfoo@1 XOR Zbar@2)" },
207  { "(foo XOR (bar AND baz)", "(Zfoo@1 XOR (Zbar@2 AND Zbaz@3))" },
208  { "(foo XOR (bar AND baz", "(Zfoo@1 XOR (Zbar@2 AND Zbaz@3))" },
209  // Slightly arbitrarily we accept mismatched quotes.
210  { "\"curly quotes”", "(curly@1 PHRASE 2 quotes@2)" },
211  { "“curly quotes\"", "(curly@1 PHRASE 2 quotes@2)" },
212  { "“curly quotes“", "(curly@1 PHRASE 2 quotes@2)" },
213  { "”curly quotes”", "(curly@1 PHRASE 2 quotes@2)" },
214  { "author:“orwell” title:“animal\"", "(Aorwell@1 OR XTanimal@2)" },
215  { "author:\"orwell” title:“animal”", "(Aorwell@1 OR XTanimal@2)" },
216  { "author:“milne, a.a.”", "(Amilne@1 PHRASE 3 Aa@2 PHRASE 3 Aa@3)" },
217  { "author:“milne, a.a.\"", "(Amilne@1 PHRASE 3 Aa@2 PHRASE 3 Aa@3)" },
218  { "author:\"milne a.a.”", "(Amilne@1 PHRASE 3 Aa@2 PHRASE 3 Aa@3)" },
219  { "“hello world” +python", "(Zpython@3 AND_MAYBE (hello@1 PHRASE 2 world@2))" },
220  { "unmatched“", "Zunmatch@1" },
221  { "unmatched”", "Zunmatch@1" },
222  { "unmatched “ ” ", "Zunmatch@1" },
223  { "unmatched \" ” ", "Zunmatch@1" },
224  { "unmatched “ \" ", "Zunmatch@1" },
225  { "hyphen-ated“ ", "(hyphen@1 PHRASE 2 ated@2)" },
226  { "hyphen-ated” ", "(hyphen@1 PHRASE 2 ated@2)" },
227  { "hyphen-ated“ ”", "(hyphen@1 PHRASE 2 ated@2)" },
228  { "hyphen-ated“ \"", "(hyphen@1 PHRASE 2 ated@2)" },
229  { "hyphen-ated\" ”", "(hyphen@1 PHRASE 2 ated@2)" },
230  { "“1.4”", "1.4@1" },
231  { "“1.\"", "1@1" },
232  { "\"A#.B.”", "(a#@1 PHRASE 2 b@2)" },
233  { "“ Xapian QueryParser” parses queries", "((xapian@1 PHRASE 2 queryparser@2) OR (Zpars@3 OR Zqueri@4))" },
234  { "“ xapian queryParser\" parses queries", "((xapian@1 PHRASE 2 queryparser@2) OR (Zpars@3 OR Zqueri@4))" },
235  { "beer NOT “orange juice”", "(Zbeer@1 AND_NOT (orange@2 PHRASE 2 juice@3))" },
236  { "“missing quote", "(missing@1 PHRASE 2 quote@2)" },
237  { "+“must have” optional", "((must@1 PHRASE 2 have@2) AND_MAYBE Zoption@3)" },
238  { "category:“Hello world”", "0 * XCAT:Hello world" },
239  { "category:“literal \"\"”", "0 * XCATliteral \"" },
240  { "category:“ ”", "0 * XCAT " },
241  { "category:\" ”\"", "0 * XCAT ”" },
242  { "category:\" ”", "0 * XCAT ”" },
243  { "category:“ \"", "0 * XCAT " },
244  { "category:“”", "0 * XCAT" },
245  { "category:\"”\"", "0 * XCAT”" },
246  { "category:\"”", "0 * XCAT”" },
247  { "category:“\"", "0 * XCAT" },
248  { "category:“(unterminated)", "0 * XCAT(unterminated)" },
249  // Real world examples from tweakers.net:
250  { "Call to undefined function: imagecreate()", "(call@1 OR Zto@2 OR Zundefin@3 OR Zfunction@4 OR imagecreate@5)" },
251  { "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))" },
252  { "php date() nedelands", "(Zphp@1 OR date@2 OR Znedeland@3)" },
253  { "wget domein --http-user", "(Zwget@1 OR Zdomein@2 OR (http@3 PHRASE 2 user@4))" },
254  { "@home problemen", "(Zhome@1 OR Zproblemen@2)" },
255  { "'ipacsum'", "Zipacsum@1" },
256  { "canal + ", "Zcanal@1" },
257  { "/var/run/mysqld/mysqld.sock", "(var@1 PHRASE 5 run@2 PHRASE 5 mysqld@3 PHRASE 5 mysqld@4 PHRASE 5 sock@5)" },
258  { "\"QSI-161 drivers\"", "(qsi@1 PHRASE 3 161@2 PHRASE 3 drivers@3)" },
259  { "\"e-cube\" barebone", "((e@1 PHRASE 2 cube@2) OR Zbarebon@3)" },
260  { "\"./httpd: symbol not found: dlopen\"", "(httpd@1 PHRASE 5 symbol@2 PHRASE 5 not@3 PHRASE 5 found@4 PHRASE 5 dlopen@5)" },
261  { "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)" },
262  { "location.href = \"\"", "(location@1 PHRASE 2 href@2)" },
263  { "method=\"post\" action=\"\">", "(method@1 OR post@2 OR action@3)" },
264  { "behuizing 19\" inch", "(Zbehuiz@1 OR 19@2 OR inch@3)" },
265  { "19\" rack", "(19@1 OR rack@2)" },
266  { "3,5\" mainboard", "(3,5@1 OR mainboard@2)" },
267  { "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)" },
268  { "data error (clic redundancy check)", "(Zdata@1 OR Zerror@2 OR (Zclic@3 OR Zredund@4 OR Zcheck@5))" },
269  { "? mediaplayer 9\"", "(Zmediaplay@1 OR 9@2)" },
270  { "date(\"w\")", "(date@1 OR w@2)" },
271  { "Syntaxisfout (operator ontbreekt ASP", "(syntaxisfout@1 OR (Zoper@2 OR Zontbreekt@3 OR asp@4))" },
272  { "Request.ServerVariables(\"logon_user\")", "((request@1 PHRASE 2 servervariables@2) OR logon_user@3)" },
273  { "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))" },
274  { "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))" },
275  { "ip_masq_new(proto=TCP)", "(ip_masq_new@1 OR proto@2 OR tcp@3)" },
276  { "\"document.write(\"", "(document@1 PHRASE 2 write@2)" },
277  { "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))" },
278  { "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))" },
279  { "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))" },
280  { "\"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)" },
281  { "\") 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)" },
282  { "\"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)" },
283  { "$structure = imap_header($mbox, $tt);", "(Zstructur@1 OR imap_header@2 OR Zmbox@3 OR Ztt@4)" },
284  { "\"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)" },
285  { "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))" },
286  { "ereg_replace(\"\\\\\",\"\\/\"", "ereg_replace@1" },
287  { "\\\\\"divx+geen+geluid\\\\\"", "(divx@1 PHRASE 3 geen@2 PHRASE 3 geluid@3)" },
288  { "lcase(\"string\")", "(lcase@1 OR string@2)" },
289  { "isEmpty( ) functie in visual basic", "(isempty@1 OR (Zfuncti@2 OR Zin@3 OR Zvisual@4 OR Zbasic@5))" },
290  { "*** stop: 0x0000001E (0xC0000005,0x00000000,0x00000000,0x00000000)", "(Zstop@1 OR 0x0000001e@2 OR 0xc0000005,0x00000000,0x00000000,0x00000000@3)" },
291  { "\"ctrl+v+c+a fout\"", "(ctrl@1 PHRASE 5 v@2 PHRASE 5 c@3 PHRASE 5 a@4 PHRASE 5 fout@5)" },
292  { "Server.CreateObject(\"ADODB.connection\")", "((server@1 PHRASE 2 createobject@2) OR (adodb@3 PHRASE 2 connection@4))" },
293  { "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))" },
294  { "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))" },
295  { "delphi CreateOleObject(\"MSXML2.DomDocument\")", "(Zdelphi@1 OR createoleobject@2 OR (msxml2@3 PHRASE 2 domdocument@4))" },
296  { "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))" },
297  { "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))" },
298  { "asp ' en \"", "(Zasp@1 OR Zen@2)" },
299  { "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)))" },
300  { "session_set_cookie_params(echo \"hoi\")", "(session_set_cookie_params@1 OR Zecho@2 OR hoi@3)" },
301  { "windows update werkt niet (windows se", "(Zwindow@1 OR Zupdat@2 OR Zwerkt@3 OR Zniet@4 OR (Zwindow@5 OR Zse@6))" },
302  { "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))" },
303  { "sony +(u20 u-20)", "((Zu20@2 OR (u@3 PHRASE 2 20@4)) AND_MAYBE Zsoni@1)" },
304  { "[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)))" },
305  { "directories lokaal php (uitlezen OR inladen)", "(Zdirectori@1 OR Zlokaal@2 OR Zphp@3 OR (Zuitlezen@4 OR Zinladen@5))" },
306  { "(multi pc modem)+ (line sync)", "(Zmulti@1 OR Zpc@2 OR Zmodem@3 OR (Zline@4 OR Zsync@5))" },
307  { "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))" },
308  { "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))" },
309  { "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))" },
310  { "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))" },
311  { "\"arp -s\" ip veranderen", "((arp@1 PHRASE 2 s@2) OR (Zip@3 OR Zveranderen@4))" },
312  { "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))" },
313  { "$datum = date(\"d-m-Y\");", "(Zdatum@1 OR date@2 OR (d@3 PHRASE 3 m@4 PHRASE 3 y@5))" },
314  { "\"'\" +asp", "Zasp@1" },
315  { "+session +[", "Zsession@1" },
316  { "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))" },
317  { "\"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)" },
318  { "\"+irq +veranderen +xp\"", "(irq@1 PHRASE 3 veranderen@2 PHRASE 3 xp@3)" },
319  { "\"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)" },
320  { "mkdir() failed (File exists) php", "(mkdir@1 OR Zfail@2 OR (file@3 OR Zexist@4) OR Zphp@5)" },
321  { "laatsteIndex(int n)", "(laatsteindex@1 OR (Zint@2 OR Zn@3))" },
322  { "\"line+in\" OR \"c8783\"", "((line@1 PHRASE 2 in@2) OR c8783@3)" },
323  { "if ($_POST['Submit'])", "(Zif@1 OR (_post@2 OR submit@3))" },
324  { "NEC DVD+-RW ND-1300A", "(nec@1 OR (dvd+@2 PHRASE 2 rw@3) OR (nd@4 PHRASE 2 1300a@5))" },
325  { "*String not found* (*String not found*.)", "(string@1 OR Znot@2 OR found@3 OR (string@4 OR Znot@5 OR found@6))" },
326  { "MSI G4Ti4200-TD 128MB (GeForce4 Ti4200)", "(msi@1 OR (g4ti4200@2 PHRASE 2 td@3) OR 128mb@4 OR (geforce4@5 OR ti4200@6))" },
327  { "href=\"#\"", "href@1" },
328  { "Request.ServerVariables(\"REMOTE_USER\") javascript", "((request@1 PHRASE 2 servervariables@2) OR remote_user@3 OR Zjavascript@4)" },
329  { "XF86Config(-4) waar", "(xf86config@1 OR 4@2 OR Zwaar@3)" },
330  { "Unknown (tag 2000)", "(unknown@1 OR (Ztag@2 OR 2000@3))" },
331  { "KT4V(MS-6712)", "(kt4v@1 OR (ms@2 PHRASE 2 6712@3))" },
332  { "scheduled+AND+nieuwsgroepen+AND+updaten", "(Zschedul@1 AND Znieuwsgroepen@2 AND Zupdaten@3)" },
333  { "137(netbios-ns)", "(137@1 OR (netbios@2 PHRASE 2 ns@3))" },
334  { "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))" },
335  { "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)" },
336  { "wat is code van \" teken", "(Zwat@1 OR Zis@2 OR Zcode@3 OR Zvan@4 OR teken@5)" },
337  { "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))" },
338  { "Permission denied (publickey,password,keyboard-interactive).", "(permission@1 OR Zdeni@2 OR (Zpublickey@3 OR Zpassword@4 OR (keyboard@5 PHRASE 2 interactive@6)))" },
339  { "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))" },
340  { "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)" },
341  { "\"2020 NEAR zoom\"", "(2020@1 PHRASE 3 near@2 PHRASE 3 zoom@3)" },
342  { "setcookie(\"naam\",\"$user\");", "(setcookie@1 OR naam@2 OR user@3)" },
343  { "MSI 645 Ultra (MS-6547) Ver1", "(msi@1 OR 645@2 OR ultra@3 OR (ms@4 PHRASE 2 6547@5) OR ver1@6)" },
344  { "if ($HTTP", "(Zif@1 OR http@2)" },
345  { "data error(cyclic redundancy check)", "(Zdata@1 OR error@2 OR (Zcyclic@3 OR Zredund@4 OR Zcheck@5))" },
346  { "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)" },
347  { "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))" },
348  { "Call Shell(\"notepad.exe\",", "(call@1 OR shell@2 OR (notepad@3 PHRASE 2 exe@4))" },
349  { "2.5\" harddisk converter", "(2.5@1 OR (harddisk@2 PHRASE 2 converter@3))" },
350  { "creative labs \"dvd+rw\"", "(Zcreativ@1 OR Zlab@2 OR (dvd@3 PHRASE 2 rw@4))" },
351  { "\"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)" },
352  { "ati radeon \"driver cleaner", "(Zati@1 OR Zradeon@2 OR (driver@3 PHRASE 2 cleaner@4))" },
353  { "\"../\" path", "Zpath@1" },
354  { "(novell client) workstation only", "(Znovel@1 OR Zclient@2 OR (Zworkstat@3 OR Zonli@4))" },
355  { "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)" },
356  { "\"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)" },
357  { "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)" },
358  { "Forwarden van domeinnaam (naar HTTP adres)", "(forwarden@1 OR Zvan@2 OR Zdomeinnaam@3 OR (Znaar@4 OR http@5 OR Zadr@6))" },
359  { "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))" },
360  { "httpd (no pid file) not running", "(Zhttpd@1 OR (Zno@2 OR Zpid@3 OR Zfile@4) OR (Znot@5 OR Zrun@6))" },
361  { "apache httpd (pid file) not running", "(Zapach@1 OR Zhttpd@2 OR (Zpid@3 OR Zfile@4) OR (Znot@5 OR Zrun@6))" },
362  { "Klasse is niet geregistreerd (Fout=80040154).", "(klasse@1 OR Zis@2 OR Zniet@3 OR Zgeregistreerd@4 OR (fout@5 OR 80040154@6))" },
363  { "\"dvd+r\" \"dvd-r\"", "((dvd@1 PHRASE 2 r@2) OR (dvd@3 PHRASE 2 r@4))" },
364  { "\"=\" tekens uit csvfile", "(Zteken@1 OR Zuit@2 OR Zcsvfile@3)" },
365  { "libc.so.6(GLIBC_2.3)", "((libc@1 PHRASE 3 so@2 PHRASE 3 6@3) OR glibc_2.3@4)" },
366  { "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))" },
367  { "(t-mobile) bereik", "((t@1 PHRASE 2 mobile@2) OR Zbereik@3)" },
368  { "error LNK2001: unresolved external symbol \"public", "(Zerror@1 OR lnk2001@2 OR Zunresolv@3 OR Zextern@4 OR Zsymbol@5 OR public@6)" },
369  { "patch linux exploit -p)", "(Zpatch@1 OR Zlinux@2 OR Zexploit@3 OR Zp@4)" },
370  { "MYD not found (Errcode: 2)", "(myd@1 OR Znot@2 OR Zfound@3 OR (errcode@4 OR 2@5))" },
371  { "ob_start(\"ob_gzhandler\"); file download", "(ob_start@1 OR ob_gzhandler@2 OR (Zfile@3 OR Zdownload@4))" },
372  { "ECS Elitegroup K7VZA (VIA VT8363/VT8363A)", "(ecs@1 OR elitegroup@2 OR k7vza@3 OR (via@4 OR (vt8363@5 PHRASE 2 vt8363a@6)))" },
373  { "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))" },
374  { "Javascript:history.go(-1)", "((javascript@1 PHRASE 3 history@2 PHRASE 3 go@3) OR 1@4)" },
375  { "java :) als icon", "(Zjava@1 OR (Zal@2 OR Zicon@3))" },
376  { "onmouseover=setPointer(this", "(onmouseover@1 OR setpointer@2 OR Zthis@3)" },
377  { "\" in vbscript", "(in@1 PHRASE 2 vbscript@2)" },
378  { "IRC (FAQ OR (hulp NEAR bij))", "(irc@1 OR (faq@2 OR (hulp@3 NEAR 11 bij@4)))" },
379  { "setProperty(\"McSquare\"+i, _xscale, _xscale++);", "(setproperty@1 OR mcsquare@2 OR Zi@3 OR _xscale@4 OR _xscale++@5)" },
380  { "[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)" },
381  { "(php.ini) (memory_limit)", "((php@1 PHRASE 2 ini@2) OR Zmemory_limit@3)" },
382  { "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)" },
383  { "VXD NAVEX()@)", "(vxd@1 OR navex@2)" },
384  { "\"Iiyama AS4314UT 17\" \"", "(iiyama@1 PHRASE 3 as4314ut@2 PHRASE 3 17@3)" },
385  { "include (\"$id.html\");", "(Zinclud@1 OR (id@2 PHRASE 2 html@3))" },
386  { "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)" },
387  { "(program files\\common) opstarten", "(Zprogram@1 OR (files@2 PHRASE 2 common@3) OR Zopstarten@4)" },
388  { "java \" string", "(Zjava@1 OR string@2)" },
389  { "+=", "" },
390  { "php +=", "Zphp@1" },
391  { "[php] ereg_replace(\".\"", "(Zphp@1 OR ereg_replace@2)" },
392  { "\"echo -e\" kleur", "((echo@1 PHRASE 2 e@2) OR Zkleur@3)" },
393  { "adobe premiere \"-1\"", "(Zadob@1 OR Zpremier@2 OR 1@3)" },
394  { "DVD brander \"+\" en \"-\"", "(dvd@1 OR Zbrander@2 OR Zen@3)" },
395  { "inspirion \"dvd+R\"", "(Zinspirion@1 OR (dvd@2 PHRASE 2 r@3))" },
396  { "asp 0x80040E14)", "(Zasp@1 OR 0x80040e14@2)" },
397  { "\"e-tech motorola router", "(e@1 PHRASE 4 tech@2 PHRASE 4 motorola@3 PHRASE 4 router@4)" },
398  { "bluetooth '1.3.2.19\"", "(Zbluetooth@1 OR 1.3.2.19@2)" },
399  { "ms +-connect", "(Zms@1 OR Zconnect@2)" },
400  { "php+print+\"", "(Zphp@1 OR print+@2)" },
401  { "athlon 1400 :welke videokaart\"", "(Zathlon@1 OR 1400@2 OR (Zwelk@3 OR videokaart@4))" },
402  { "+-dvd", "Zdvd@1" },
403  { "glftpd \"-new-\"", "(Zglftpd@1 OR new@2)" },
404  { "\"scandisk + dos5.0", "(scandisk@1 PHRASE 2 dos5.0@2)" },
405  { "socket\\(\\)", "socket@1" },
406  { "msn (e-tech) router", "(Zmsn@1 OR (e@2 PHRASE 2 tech@3) OR Zrouter@4)" },
407  { "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)" },
408  { "\"CF+bluetooth\"", "(cf@1 PHRASE 2 bluetooth@2)" },
409  { "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))" },
410  { "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)" },
411  { "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))" },
412  { "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)))" },
413  { "\"muziek 2x zo snel\"\"", "(muziek@1 PHRASE 4 2x@2 PHRASE 4 zo@3 PHRASE 4 snel@4)" },
414  { "execCommand('inserthorizontalrule'", "(execcommand@1 OR Zinserthorizontalrul@2)" },
415  { "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))" },
416  { "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)" },
417  { "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))" },
418  { "'\"><br>bla</br>", "(br@1 PHRASE 3 bla@2 PHRASE 3 br@3)" },
419  { "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))" },
420  { "\"(P5A-b)\"", "(p5a@1 PHRASE 2 b@2)" },
421  { "(13,5 > 13) == no-go!", "(13,5@1 OR 13@2 OR (no@3 PHRASE 2 go@4))" },
422  { "eth not found \"ifconfig -a\"", "(Zeth@1 OR Znot@2 OR Zfound@3 OR (ifconfig@4 PHRASE 2 a@5))" },
423  { "<META NAME=\"ROBOTS", "(meta@1 OR name@2 OR robots@3)" },
424  { "lp0: using parport0 (interrupt-driven)", "(Zlp0@1 OR (Zuse@2 OR Zparport0@3) OR (interrupt@4 PHRASE 2 driven@5))" },
425  { "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)" },
426  { "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))" },
427  { "header(\"Content Type: text/html\");", "(header@1 OR (content@2 OR type@3) OR (text@4 PHRASE 2 html@5))" },
428  { "\"-RW\" \"+RW\"", "(rw@1 OR rw@2)" },
429  { "\"cresta digital answering machine", "(cresta@1 PHRASE 4 digital@2 PHRASE 4 answering@3 PHRASE 4 machine@4)" },
430  { "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)))" },
431  { "c++ fopen \"r+t\"", "(Zc++@1 OR Zfopen@2 OR (r@3 PHRASE 2 t@4))" },
432  { "c++ fopen (r+t)", "(Zc++@1 OR Zfopen@2 OR (Zr@3 OR Zt@4))" },
433  { "\"DVD+R\"", "(dvd@1 PHRASE 2 r@2)" },
434  { "Class.forName(\"jdbc.odbc.JdbcOdbcDriver\");", "((class@1 PHRASE 2 forname@2) OR (jdbc@3 PHRASE 3 odbc@4 PHRASE 3 jdbcodbcdriver@5))" },
435  { "perl(find.pl)", "(perl@1 OR (find@2 PHRASE 2 pl@3))" },
436  { "\"-5v\" voeding", "(5v@1 OR Zvoed@2)" },
437  { "\"-5v\" power supply", "(5v@1 OR (Zpower@2 OR Zsuppli@3))" },
438  { "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))" },
439  { "(error $2108) Borland", "(Zerror@1 OR 2108@2 OR borland@3)" },
440  { "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))" },
441  { "Elektrotechniek + \"hoe bevalt het?\"\"", "(elektrotechniek@1 OR (hoe@2 PHRASE 3 bevalt@3 PHRASE 3 het@4))" },
442  { "Shortcuts in menu (java", "(shortcuts@1 OR Zin@2 OR Zmenu@3 OR Zjava@4)" },
443  { "detonator+settings\"", "(Zdeton@1 OR settings@2)" },
444  { "(ez-bios) convert", "((ez@1 PHRASE 2 bios@2) OR Zconvert@3)" },
445  { "Sparkle 7100M4 64MB (GeForce4 MX440)", "(sparkle@1 OR 7100m4@2 OR 64mb@3 OR (geforce4@4 OR mx440@5))" },
446  { "freebsd \"boek OR newbie\"", "(Zfreebsd@1 OR (boek@2 PHRASE 3 or@3 PHRASE 3 newbie@4))" },
447  { "for (;;) c++", "(Zfor@1 OR Zc++@2)" },
448  { "1700+-2100+", "(1700+@1 PHRASE 2 2100+@2)" },
449  { "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))" },
450  { "NEC DV-5800B (Bul", "(nec@1 OR (dv@2 PHRASE 2 5800b@3) OR bul@4)" },
451  { "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))" },
452  { "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))" },
453  { "'q ben\"", "(Zq@1 OR ben@2)" },
454  { "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))" },
455  { "\xc3\xb6ude onderdelen\"", "(Z\xc3\xb6ude@1 OR onderdelen@2)" },
456  { "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))" },
457  { "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))" },
458  { "cybercom \"dvd+r\"", "(Zcybercom@1 OR (dvd@2 PHRASE 2 r@3))" },
459  { "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))" },
460  { "relais +/-", "Zrelai@1" },
461  { "formules (slepen OR doortrekken) excel", "(Zformul@1 OR (Zslepen@2 OR Zdoortrekken@3) OR Zexcel@4)" },
462  { "\"%English", "english@1" },
463  { "select max( mysql", "(Zselect@1 OR max@2 OR Zmysql@3)" },
464  { "leejow(saait", "(leejow@1 OR Zsaait@2)" },
465  { "'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))" },
466  { "K7T Turbo 2 (MS-6330)", "(k7t@1 OR turbo@2 OR 2@3 OR (ms@4 PHRASE 2 6330@5))" },
467  { "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))" },
468  { "\"cannot find -lz\"", "(cannot@1 PHRASE 3 find@2 PHRASE 3 lz@3)" },
469  { "undefined reference to `mysql_drop_db'\"", "(Zundefin@1 OR Zrefer@2 OR Zto@3 OR Zmysql_drop_db@4)" },
470  { "search form asp \"%'", "(Zsearch@1 OR Zform@2 OR Zasp@3)" },
471  { "(dvd+r) kwaliteit", "(Zdvd@1 OR Zr@2 OR Zkwaliteit@3)" },
472  { "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))" },
473  { "geluid (schokt OR hapert)", "(Zgeluid@1 OR (Zschokt@2 OR Zhapert@3))" },
474  { "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)" },
475  { "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))" },
476  { "(library qt-mt) not found", "(Zlibrari@1 OR (qt@2 PHRASE 2 mt@3) OR (Znot@4 OR Zfound@5))" },
477  { "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))" },
478  { "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))" },
479  { "Titan TTC-D5TB(4/CU35)", "(titan@1 OR (ttc@2 PHRASE 2 d5tb@3) OR (4@4 PHRASE 2 cu35@5))" },
480  { "[php] date( min", "(Zphp@1 OR date@2 OR Zmin@3)" },
481  { "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)" },
482  { "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))" },
483  { "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))" },
484  { "ati linux drivers (4.3.0)", "(Zati@1 OR Zlinux@2 OR Zdriver@3 OR 4.3.0@4)" },
485  { "ENCAPSED_AND_WHITESPACE", "encapsed_and_whitespace@1" },
486  { "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))" },
487  { "welke dvd \"+r\" media", "(Zwelk@1 OR Zdvd@2 OR r@3 OR Zmedia@4)" },
488  { "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))" },
489  { "dvd +/-", "Zdvd@1" },
490  { "7vaxp +voltage mod\"", "(Zvoltag@2 AND_MAYBE (7vaxp@1 OR mod@3))" },
491  { "lpt port (SPP/EPP) is enabled", "(Zlpt@1 OR Zport@2 OR (spp@3 PHRASE 2 epp@4) OR (Zis@5 OR Zenabl@6))" },
492  { "getenv(\"HTTP_REFERER\")", "(getenv@1 OR http_referer@2)" },
493  { "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)" },
494  { "Exception number: c0000005 (access violation)", "(exception@1 OR Znumber@2 OR Zc0000005@3 OR (Zaccess@4 OR Zviolat@5))" },
495  { "header(\"Content-type:application/octetstream\");", "(header@1 OR (content@2 PHRASE 4 type@3 PHRASE 4 application@4 PHRASE 4 octetstream@5))" },
496  { "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)))" },
497  { "(001.part.met", "(001@1 PHRASE 3 part@2 PHRASE 3 met@3)" },
498  { "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))" },
499  { "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)" },
500  { "dvd \"+\" \"-\"", "Zdvd@1" },
501  { "bericht ( %)", "Zbericht@1" },
502  { "2500+ of 2600+ (niett OC)", "(2500+@1 OR Zof@2 OR 2600+@3 OR (Zniett@4 OR oc@5))" },
503  { "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))" },
504  { "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))" },
505  { "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))" },
506  { "GA-8IHXP(3.0)", "((ga@1 PHRASE 2 8ihxp@2) OR 3.0@3)" },
507  { "8IHXP(3.0)", "(8ihxp@1 OR 3.0@2)" },
508  { "na\xc2\xb7si (de ~ (m.))", "(Zna\xc2\xb7si@1 OR (Zde@2 OR Zm@3))" },
509  { "header(\"Content-Disposition: attachment;", "(header@1 OR (content@2 PHRASE 3 disposition@3 PHRASE 3 attachment@4))" },
510  { "\"header(\"Content-Disposition: attachment;\"", "(header@1 OR (content@2 PHRASE 2 disposition@3) OR Zattach@4)" },
511  { "\"Beep -f\"", "(beep@1 PHRASE 2 f@2)" },
512  { "kraan NEAR (Elektrisch OR Electrisch)", "(Zkraan@1 OR near@2 OR (elektrisch@3 OR or@4 OR electrisch@5))" },
513  { "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))" },
514  { "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))" },
515  { "ac3 (0x2000) \"Dolby Laboratories,", "(Zac3@1 OR 0x2000@2 OR (dolby@3 PHRASE 2 laboratories@4))" },
516  { "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))" },
517  { "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))" },
518  { "Motion JPEG (MJPEG codec)", "(motion@1 OR jpeg@2 OR (mjpeg@3 OR Zcodec@4))" },
519  { ": zoomtext\"", "zoomtext@1" },
520  { "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))" },
521  { "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))" },
522  { "\"\"wa is fase\"", "(Zwa@1 OR Zis@2 OR fase@3)" },
523  { "<v:imagedata src=\"", "((v@1 PHRASE 2 imagedata@2) OR src@3)" },
524  { "system(play ringin.wav); ?>", "(system@1 OR Zplay@2 OR (ringin@3 PHRASE 2 wav@4))" },
525  { "\"perfect NEAR systems\"", "(perfect@1 PHRASE 3 near@2 PHRASE 3 systems@3)" },
526  { "LoadLibrary(\"mainta/gamex86.dll\") failed", "(loadlibrary@1 OR (mainta@2 PHRASE 3 gamex86@3 PHRASE 3 dll@4) OR Zfail@5)" },
527  { "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)" },
528  { "secundaire IDE-controller (dubbele fifo)", "(Zsecundair@1 OR (ide@2 PHRASE 2 controller@3) OR (Zdubbel@4 OR Zfifo@5))" },
529  { "\"Postal2+Explorer.exe\"", "(postal2@1 PHRASE 3 explorer@2 PHRASE 3 exe@3)" },
530  { "COUNT(*)", "count@1" },
531  { "Nuttige Windows progs (1/11)", "(nuttige@1 OR windows@2 OR Zprog@3 OR (1@4 PHRASE 2 11@5))" },
532  { "if(usercode==passcode==)", "(if@1 OR usercode@2 OR passcode@3)" },
533  { "lg 8160b (dvd+r)", "(Zlg@1 OR 8160b@2 OR (Zdvd@3 OR Zr@4))" },
534  { "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)))" },
535  { "'ipod pakt tags niet\"", "(Zipod@1 OR Zpakt@2 OR Ztag@3 OR niet@4)" },
536  { "\"DVD+/-R\"", "(dvd+@1 PHRASE 2 r@2)" },
537  { "\"DVD+R DVD-R\"", "(dvd@1 PHRASE 4 r@2 PHRASE 4 dvd@3 PHRASE 4 r@4)" },
538  { "php ;) in een array zetten", "(Zphp@1 OR (Zin@2 OR Zeen@3 OR Zarray@4 OR Zzetten@5))" },
539  { "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)" },
540  { "creative (soundblaster OR sb) 128", "(Zcreativ@1 OR (Zsoundblast@2 OR Zsb@3) OR 128@4)" },
541  { "Can't open file: (errno: 145)", "(can't@1 OR Zopen@2 OR Zfile@3 OR (Zerrno@4 OR 145@5))" },
542  { "Formateren lukt niet(98,XP)", "(formateren@1 OR Zlukt@2 OR niet@3 OR 98@4 OR xp@5)" },
543  { "access denied (java.io.", "(Zaccess@1 OR Zdeni@2 OR (java@3 PHRASE 2 io@4))" },
544  { "(access denied (java.io.)", "(Zaccess@1 OR Zdeni@2 OR (java@3 PHRASE 2 io@4))" },
545  { "wil niet installeren ( crc fouten)", "(Zwil@1 OR Zniet@2 OR Zinstalleren@3 OR (Zcrc@4 OR Zfouten@5))" },
546  { "(DVD+RW) brandsoftware meerdere", "(dvd@1 OR rw@2 OR (Zbrandsoftwar@3 OR Zmeerder@4))" },
547  { "(database OF databases) EN geheugen", "(Zdatabas@1 OR of@2 OR Zdatabas@3 OR (en@4 OR Zgeheugen@5))" },
548  { "(server 2003) winroute", "(Zserver@1 OR 2003@2 OR Zwinrout@3)" },
549  { "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))" },
550  { "(draadloos OR wireless) netwerk", "(Zdraadloo@1 OR Zwireless@2 OR Znetwerk@3)" },
551  { "localtime(time(NULL));", "(localtime@1 OR time@2 OR null@3)" },
552  { "ob_start(\"ob_gzhandler\");", "(ob_start@1 OR ob_gzhandler@2)" },
553  { "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))" },
554  { "COM+-gebeurtenissysteem", "(com+@1 PHRASE 2 gebeurtenissysteem@2)" },
555  { "rcpthosts (#5.7.1)", "(Zrcpthost@1 OR 5.7.1@2)" },
556  { "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))" },
557  { "window.open( scrollbar", "((window@1 PHRASE 2 open@2) OR Zscrollbar@3)" },
558  { "T68i truc ->", "(t68i@1 OR Ztruc@2)" },
559  { "T68i ->", "t68i@1" },
560  { "\"de lijn is bezet\"\"", "(de@1 PHRASE 4 lijn@2 PHRASE 4 is@3 PHRASE 4 bezet@4)" },
561  { "if (eregi(\"", "(Zif@1 OR eregi@2)" },
562  { "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))" },
563  { "execCommand(\"Paste\");", "(execcommand@1 OR paste@2)" },
564  { "\"-1 unread\"", "(1@1 PHRASE 2 unread@2)" },
565  { "\"www.historical-fire-engines", "(www@1 PHRASE 4 historical@2 PHRASE 4 fire@3 PHRASE 4 engines@4)" },
566  { "\"DVD+RW\" erase", "((dvd@1 PHRASE 2 rw@2) OR Zeras@3)" },
567  { "[showjekamer)", "Zshowjekam@1" },
568  { "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)" },
569  { "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))" },
570  { "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))" },
571  { "vervangen # \"/", "Zvervangen@1" },
572  { "vervangen # /\"", "Zvervangen@1" },
573  { "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)" },
574  { "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))" },
575  { "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))" },
576  { "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))" },
577  { "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))" },
578  { "mag mijn waarschuwing nu weg ? ;)", "(Zmag@1 OR Zmijn@2 OR Zwaarschuw@3 OR Znu@4 OR Zweg@5)" },
579  { "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))" },
580  { "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))" },
581  { "(browser 19) citrix", "(Zbrowser@1 OR 19@2 OR Zcitrix@3)" },
582  { "preg_replace (.*?)", "Zpreg_replac@1" },
583  { "formule excel #naam\"?\"", "(Zformul@1 OR Zexcel@2 OR naam@3)" },
584  { "->", "" },
585  { "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))" },
586  { "<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))" },
587  { "\"rpm -e httpd\"", "(rpm@1 PHRASE 3 e@2 PHRASE 3 httpd@3)" },
588  { "automatisch op All Flis (*.*)", "(Zautomatisch@1 OR Zop@2 OR all@3 OR flis@4)" },
589  { "(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))" },
590  { "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))" },
591  { "\"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)" },
592  { "(./) chmod.sh", "(chmod@1 PHRASE 2 sh@2)" },
593  { "document.write(ssg(\" html", "((document@1 PHRASE 2 write@2) OR ssg@3 OR html@4)" },
594  { "superstack \"mac+adressen\"", "(Zsuperstack@1 OR (mac@2 PHRASE 2 adressen@3))" },
595  { "IIS getenv(REMOTE_HOST)_", "(iis@1 OR getenv@2 OR remote_host@3 OR _@4)" },
596  { "IIS en getenv(REMOTE_HOST)", "(iis@1 OR Zen@2 OR getenv@3 OR remote_host@4)" },
597  { "php getenv(\"HTTP_REFERER\")", "(Zphp@1 OR getenv@2 OR http_referer@3)" },
598  { "nec+-1300", "(nec+@1 PHRASE 2 1300@2)" },
599  { "smbpasswd script \"-s\"", "(Zsmbpasswd@1 OR Zscript@2 OR s@3)" },
600  { "leestekens \" \xc3\xb6 \xc3\xab", "(Zleesteken@1 OR (\xc3\xb6@2 PHRASE 2 \xc3\xab@3))" },
601  { "freesco and (all seeing eye)", "(Zfreesco@1 OR Zand@2 OR (Zall@3 OR Zsee@4 OR Zeye@5))" },
602  { "('all seeing eye') and freesco", "(Zall@1 OR Zsee@2 OR Zeye@3 OR (Zand@4 OR Zfreesco@5))" },
603  { "\"[......\"", "" },
604  { "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)))" },
605  { "gegevensfout (cyclishe redundantiecontrole)", "(Zgegevensfout@1 OR (Zcyclish@2 OR Zredundantiecontrol@3))" },
606  { "firmware versie waar NEC\"", "(Zfirmwar@1 OR Zversi@2 OR Zwaar@3 OR nec@4)" },
607  { "nu.nl \"-1\"", "((nu@1 PHRASE 2 nl@2) OR 1@3)" },
608  { "provider+-webspace", "(provider+@1 PHRASE 2 webspace@2)" },
609  { "verschil \"dvd+rw\" \"dvd-rw\"", "(Zverschil@1 OR (dvd@2 PHRASE 2 rw@3) OR (dvd@4 PHRASE 2 rw@5))" },
610  { "(dhcp client) + hangt", "(Zdhcp@1 OR Zclient@2 OR Zhangt@3)" },
611  { "MSI 875P Neo-FIS2R (Intel 875P)", "(msi@1 OR 875p@2 OR (neo@3 PHRASE 2 fis2r@4) OR (intel@5 OR 875p@6))" },
612  { "voeding passief gekoeld\"", "(Zvoed@1 OR Zpassief@2 OR gekoeld@3)" },
613  { "if (mysql_num_rows($resultaat)==1)", "(Zif@1 OR mysql_num_rows@2 OR Zresultaat@3 OR 1@4)" },
614  { "Server.CreateObject(\"Persits.Upload.1\")", "((server@1 PHRASE 2 createobject@2) OR (persits@3 PHRASE 3 upload@4 PHRASE 3 1@5))" },
615  { "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))" },
616  { "if (cod>9999999", "(Zif@1 OR (cod@2 OR 9999999@3))" },
617  { "\"rm -rf /bin/laden\"", "(rm@1 PHRASE 4 rf@2 PHRASE 4 bin@3 PHRASE 4 laden@4)" },
618  { "\">>> 0) & 0xFF\"", "(0@1 PHRASE 2 0xff@2)" },
619  { "<!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))" },
620  { "<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)" },
621  { "linux humor :)", "(Zlinux@1 OR Zhumor@2)" },
622  { "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))" },
623  { "remote_smtp defer (-44)", "(Zremote_smtp@1 OR Zdefer@2 OR 44@3)" },
624  { "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)" },
625  { "Koper + amoniak (NH2", "(koper@1 OR Zamoniak@2 OR nh2@3)" },
626  { "nec dvd -/+r", "((Znec@1 OR Zdvd@2) AND_NOT Zr@3)" },
627  { "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)" },
628  { "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))" },
629  { "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))" },
630  { "\"~\" + \"c:\\\"", "Zc@1" },
631  { "mysql count(*)", "(Zmysql@1 OR count@2)" },
632  { "for %f in (*.*) do", "(Zfor@1 OR (Zf@2 OR Zin@3) OR Zdo@4)" },
633  { "raar \"~\" bestand", "(Zraar@1 OR Zbestand@2)" },
634  { "NEC DVD +-R/RW 1300", "(nec@1 OR dvd@2 OR (r@3 PHRASE 2 rw@4) OR 1300@5)" },
635  { "approved (ref: 38446-263)", "(Zapprov@1 OR (Zref@2 OR (38446@3 PHRASE 2 263@4)))" },
636  { "GA-7VRXP(2.0)", "((ga@1 PHRASE 2 7vrxp@2) OR 2.0@3)" },
637  { "~ Could not retrieve directory listing for \"/\"", "(could@1 OR Znot@2 OR Zretriev@3 OR Zdirectori@4 OR Zlist@5 OR Zfor@6)" },
638  { "asp CreateObject(\"Word.Document\")", "(Zasp@1 OR createobject@2 OR (word@3 PHRASE 2 document@4))" },
639  { "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))" },
640  { "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)))" },
641  { "parent.document.getElementById(\\\"leftmenu\\\").cols", "((parent@1 PHRASE 3 document@2 PHRASE 3 getelementbyid@3) OR leftmenu@4 OR Zcol@5)" },
642  { "<% if not isEmpty(Request.QueryString) then", "(Zif@1 OR Znot@2 OR isempty@3 OR (request@4 PHRASE 2 querystring@5) OR Zthen@6)" },
643  { "Active Desktop (Hier issie)", "(active@1 OR desktop@2 OR (hier@3 OR Zissi@4))" },
644  { "Asus A7V8X (LAN + Sound)", "(asus@1 OR a7v8x@2 OR (lan@3 OR sound@4))" },
645  { "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)" },
646  { "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)" },
647  { "session_register(\"login\");", "(session_register@1 OR login@2)" },
648  { "\"kylix+ndmb\"", "(kylix@1 PHRASE 2 ndmb@2)" },
649  { "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))" },
650  { "If ($_SESSION[\"Login\"] == 1)", "(if@1 OR (_session@2 OR login@3 OR 1@4))" },
651  { "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))" },
652  { "ASRock K7VT2 (incl. LAN)", "(asrock@1 OR k7vt2@2 OR (Zincl@3 OR lan@4))" },
653  { "+windows98 +(geen communicatie) +ie5", "(Zwindows98@1 AND (Zgeen@2 OR Zcommunicati@3) AND Zie5@4)" },
654  { "\"xterm -fn\"", "(xterm@1 PHRASE 2 fn@2)" },
655  { "IRQL_NOT_LESS_OR_EQUAL", "irql_not_less_or_equal@1" },
656  { "access query \"NOT IN\"", "(Zaccess@1 OR Zqueri@2 OR (not@3 PHRASE 2 in@4))" },
657  { "\"phrase one \"phrase two\"", "((phrase@1 PHRASE 2 one@2) OR (Zphrase@3 OR two@4))" },
658  { "NEAR 207 46 249 27", "(near@1 OR 207@2 OR 46@3 OR 249@4 OR 27@5)" },
659  { "- NEAR 12V voeding", "(near@1 OR 12v@2 OR Zvoed@3)" },
660  { "waarom \"~\" in directorynaam", "(Zwaarom@1 OR (Zin@2 OR Zdirectorynaam@3))" },
661  { "cd'r NEAR toebehoren", "(cd'r@1 NEAR 11 toebehoren@2)" },
662  { "site:1 site:2", "0 * (H1 OR H2)" },
663  { "site:1 site2:2", "0 * (H1 AND J2)" },
664  { "site:1 site:2 site2:2", "0 * ((H1 OR H2) AND J2)" },
665  { "site:1 OR site:2", "(0 * H1 OR 0 * H2)" },
666  { "site:1 AND site:2", "(0 * H1 AND 0 * H2)" },
667  { "foo AND site:2", "(Zfoo@1 AND 0 * H2)" },
668  // Non-exclusive boolean prefixes feature tests (ticket#402):
669  { "category:1 category:2", "0 * (XCAT1 AND XCAT2)" },
670  { "category:1 site2:2", "0 * (XCAT1 AND J2)" },
671  { "category:1 category:2 site2:2", "0 * ((XCAT1 AND XCAT2) AND J2)" },
672  { "category:1 OR category:2", "(0 * XCAT1 OR 0 * XCAT2)" },
673  { "category:1 AND category:2", "(0 * XCAT1 AND 0 * XCAT2)" },
674  { "foo AND category:2", "(Zfoo@1 AND 0 * XCAT2)" },
675  // Regression test for combining multiple non-exclusive prefixes, fixed in
676  // 1.2.22 and 1.3.4.
677  { "category:1 dogegory:2", "0 * (XCAT1 AND XDOG2)" },
678  { "A site:1 site:2", "(a@1 FILTER (H1 OR H2))" },
679 #if 0
680  { "A (site:1 OR site:2)", "(a@1 FILTER (H1 OR H2))" },
681 #endif
682  { "A site:1 site2:2", "(a@1 FILTER (H1 AND J2))" },
683  { "A site:1 site:2 site2:2", "(a@1 FILTER ((H1 OR H2) AND J2))" },
684 #if 0
685  { "A site:1 OR site:2", "(a@1 FILTER (H1 OR H2))" },
686 #endif
687  { "A site:1 AND site:2", "((a@1 FILTER H1) AND 0 * H2)" },
688  { "site:xapian.org OR site:www.xapian.org", "(0 * Hxapian.org OR 0 * Hwww.xapian.org)" },
689  { "site:xapian.org site:www.xapian.org", "0 * (Hxapian.org OR Hwww.xapian.org)" },
690  { "site:xapian.org AND site:www.xapian.org", "(0 * Hxapian.org AND 0 * Hwww.xapian.org)" },
691  { "Xapian site:xapian.org site:www.xapian.org", "(xapian@1 FILTER (Hxapian.org OR Hwww.xapian.org))" },
692  { "author:richard author:olly writer:charlie", "(ZArichard@1 OR ZAolli@2 OR ZAcharli@3)"},
693  { "author:richard NEAR title:book", "(Arichard@1 NEAR 11 XTbook@2)"},
694  { "authortitle:richard NEAR title:book", "((Arichard@1 OR XTrichard@1) NEAR 11 XTbook@2)" },
695  { "multisite:xapian.org", "0 * (Hxapian.org OR Jxapian.org)"},
696  { "authortitle:richard", "(ZArichard@1 OR ZXTrichard@1)"},
697  { "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))" },
698  { "authortitle:richard-boulton", "((Arichard@1 PHRASE 2 Aboulton@2) OR (XTrichard@1 PHRASE 2 XTboulton@2))"},
699  { "authortitle:\"richard boulton\"", "((Arichard@1 PHRASE 2 Aboulton@2) OR (XTrichard@1 PHRASE 2 XTboulton@2))"},
700  // Test FLAG_NGRAMS isn't on by default:
701  { "久有归天愿", "Z久有归天愿@1" },
702  { NULL, "NGRAMS" }, // Enable FLAG_NGRAMS
703  // Test queries which don't need word break finding still parse the same:
704  { "gtk+ -gnome", "(Zgtk+@1 AND_NOT Zgnome@2)" },
705  { "“curly quotes”", "(curly@1 PHRASE 2 quotes@2)" },
706  // Test n-gram generation:
707  { "久有归天愿", "(久@1 AND 久有@1 AND 有@1 AND 有归@1 AND 归@1 AND 归天@1 AND 天@1 AND 天愿@1 AND 愿@1)" },
708  { "久有 归天愿", "((久@1 AND 久有@1 AND 有@1) OR (归@2 AND 归天@2 AND 天@2 AND 天愿@2 AND 愿@2))" },
709  { "久有!归天愿", "((久@1 AND 久有@1 AND 有@1) OR (归@2 AND 归天@2 AND 天@2 AND 天愿@2 AND 愿@2))" },
710  { "title:久有 归 天愿", "((XT久@1 AND XT久有@1 AND XT有@1) OR 归@2 OR (天@3 AND 天愿@3 AND 愿@3))" },
711  { "h众ello万众", "(Zh@1 OR 众@2 OR Zello@3 OR (万@4 AND 万众@4 AND 众@4))" },
712  { "世(の中)TEST_tm", "(世@1 OR (の@2 AND の中@2 AND 中@2) OR test_tm@3)" },
713  { "다녀 AND 와야", "(다@1 AND 다녀@1 AND 녀@1 AND (와@2 AND 와야@2 AND 야@2))" },
714  { "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))" },
715  // FIXME: These should really filter by bigrams to accelerate:
716  { "\"久有归\"", "(久@1 PHRASE 3 有@1 PHRASE 3 归@1)" },
717  { "\"久有test归\"", "(久@1 PHRASE 4 有@1 PHRASE 4 test@2 PHRASE 4 归@3)" },
718  // FIXME: this should work: { "久 NEAR 有", "(久@1 NEAR 11 有@2)" },
719 
720  // Regression tests that UNBROKEN_WORDS ends a term group:
721  { "x 我y", "(Zx@1 OR 我@2 OR Zy@3)" },
722  { "x 我 y", "(Zx@1 OR 我@2 OR Zy@3)" },
723  { "w x 我y", "(Zw@1 OR Zx@2 OR 我@3 OR Zy@4)" },
724  { "w x 我 y", "(Zw@1 OR Zx@2 OR 我@3 OR Zy@4)" },
725 
726  { NULL, NULL }
727 };
728 
729 DEFINE_TESTCASE(queryparser1, !backend) {
730  Xapian::QueryParser queryparser;
731  queryparser.set_stemmer(Xapian::Stem("english"));
733  queryparser.add_prefix("author", "A");
734  queryparser.add_prefix("writer:", "A");
735  queryparser.add_prefix("title", "XT");
736  queryparser.add_prefix("subject", "XT");
737  queryparser.add_prefix("authortitle", "A");
738  queryparser.add_prefix("authortitle", "XT");
739  queryparser.add_boolean_prefix("site", "H");
740  queryparser.add_boolean_prefix("site2:", "J");
741  queryparser.add_boolean_prefix("multisite", "H");
742  queryparser.add_boolean_prefix("multisite", "J");
743  queryparser.add_boolean_prefix("category", "XCAT", false);
744  queryparser.add_boolean_prefix("dogegory", "XDOG", false);
746  queryparser.add_boolean_prefix("authortitle", "B");
747  );
749  queryparser.add_prefix("multisite", "B");
750  );
751  unsigned flags = queryparser.FLAG_DEFAULT;
752  for (const test *p = test_or_queries; ; ++p) {
753  if (!p->query) {
754  if (!p->expect) break;
755  if (strcmp(p->expect, "NGRAMS") == 0) {
756  flags = queryparser.FLAG_DEFAULT|queryparser.FLAG_NGRAMS;
757  continue;
758  }
759  FAIL_TEST("Unknown flag code: " << p->expect);
760  }
761  string expect, parsed;
762  if (p->expect)
763  expect = p->expect;
764  else
765  expect = "parse error";
766  try {
767  Xapian::Query qobj = queryparser.parse_query(p->query, flags);
768  parsed = qobj.get_description();
769  expect = string("Query(") + expect + ')';
770  } catch (const Xapian::QueryParserError &e) {
771  parsed = e.get_msg();
772  } catch (const Xapian::Error &e) {
773  parsed = e.get_description();
774  } catch (...) {
775  parsed = "Unknown exception!";
776  }
777  tout << "Query: " << p->query << '\n';
778  TEST_STRINGS_EQUAL(parsed, expect);
779  }
780 }
781 
782 static const test test_and_queries[] = {
783  { "internet explorer title:(http www)", "(Zinternet@1 AND Zexplor@2 AND (ZXThttp@3 AND ZXTwww@4))" },
784  // Regression test for bug in 0.9.2 and earlier - this would give
785  // (two@2 AND_MAYBE (one@1 AND three@3))
786  { "one +two three", "(Zone@1 AND Ztwo@2 AND Zthree@3)" },
787  { "hello -title:\"hello world\"", "(Zhello@1 AND_NOT (XThello@2 PHRASE 2 XTworld@3))" },
788  // Regression test for bug fixed in 1.0.4 - the '-' would be ignored there
789  // because the whitespace after the '"' wasn't noticed.
790  { "\"hello world\" -C++", "((hello@1 PHRASE 2 world@2) AND_NOT c++@3)" },
791  // Regression tests for bug fixed in 1.0.4 - queries with only boolean
792  // filter and HATE terms weren't accepted.
793  { "-cup site:world", "(0 * Hworld AND_NOT Zcup@1)" },
794  { "site:world -cup", "(0 * Hworld AND_NOT Zcup@1)" },
795  // Regression test for bug fixed in 1.0.4 - the KET token for ')' was lost.
796  { "(site:world) -cup", "(0 * Hworld AND_NOT Zcup@1)" },
797  // Regression test for bug fixed in 1.0.4 - a boolean filter term between
798  // probabilistic terms caused a parse error (probably broken during the
799  // addition of synonym support in 1.0.2).
800  { "foo site:xapian.org bar", "((Zfoo@1 AND Zbar@2) FILTER Hxapian.org)" },
801  // Add coverage for other cases similar to the above.
802  { "a b site:xapian.org", "((Za@1 AND Zb@2) FILTER Hxapian.org)" },
803  { "site:xapian.org a b", "((Za@1 AND Zb@2) FILTER Hxapian.org)" },
804  { NULL, "NGRAMS" }, // Enable FLAG_NGRAMS
805  // Test n-gram generation:
806  { "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)))" },
807  { "洛伊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)" },
808  { NULL, NULL }
809 };
810 
811 // With default_op = OP_AND.
812 DEFINE_TESTCASE(qp_default_op1, !backend) {
813  Xapian::QueryParser queryparser;
814  queryparser.set_stemmer(Xapian::Stem("english"));
816  queryparser.add_prefix("author", "A");
817  queryparser.add_prefix("title", "XT");
818  queryparser.add_prefix("subject", "XT");
819  queryparser.add_boolean_prefix("site", "H");
821  unsigned flags = queryparser.FLAG_DEFAULT;
822  for (const test *p = test_and_queries; ; ++p) {
823  if (!p->query) {
824  if (!p->expect) break;
825  if (strcmp(p->expect, "NGRAMS") == 0) {
826  flags = queryparser.FLAG_DEFAULT|queryparser.FLAG_NGRAMS;
827  continue;
828  }
829  FAIL_TEST("Unknown flag code: " << p->expect);
830  }
831  string expect, parsed;
832  if (p->expect)
833  expect = p->expect;
834  else
835  expect = "parse error";
836  try {
837  Xapian::Query qobj = queryparser.parse_query(p->query, flags);
838  parsed = qobj.get_description();
839  expect = string("Query(") + expect + ')';
840  } catch (const Xapian::QueryParserError &e) {
841  parsed = e.get_msg();
842  } catch (const Xapian::Error &e) {
843  parsed = e.get_description();
844  } catch (...) {
845  parsed = "Unknown exception!";
846  }
847  tout << "Query: " << p->query << '\n';
848  TEST_STRINGS_EQUAL(parsed, expect);
849  }
850 }
851 
852 // Feature test for specify the default prefix (new in Xapian 1.0.0).
853 DEFINE_TESTCASE(qp_default_prefix1, !backend) {
855  qp.set_stemmer(Xapian::Stem("english"));
857  qp.add_prefix("title", "XT");
858 
859  Xapian::Query qobj;
860  qobj = qp.parse_query("hello world", 0, "A");
861  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((ZAhello@1 OR ZAworld@2))");
862  qobj = qp.parse_query("me title:stuff", 0, "A");
863  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((ZAme@1 OR ZXTstuff@2))");
864  qobj = qp.parse_query("title:(stuff) me", Xapian::QueryParser::FLAG_BOOLEAN, "A");
865  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((ZXTstuff@1 OR ZAme@2))");
866  qobj = qp.parse_query("英国 title:文森hello", qp.FLAG_NGRAMS, "A");
867  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))");
868 }
869 
870 // Feature test for setting the default prefix with add_prefix()
871 // (new in Xapian 1.0.3).
872 DEFINE_TESTCASE(qp_default_prefix2, !backend) {
874  qp.set_stemmer(Xapian::Stem("english"));
876 
877  // test that default prefixes can only be set with add_prefix().
879  qp.add_boolean_prefix("", "B");
880  );
881 
882  qp.add_prefix("title", "XT");
883  qp.add_prefix("", "A");
884 
885  Xapian::Query qobj;
886  qobj = qp.parse_query("hello world", 0);
887  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((ZAhello@1 OR ZAworld@2))");
888  qobj = qp.parse_query("me title:stuff", 0);
889  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((ZAme@1 OR ZXTstuff@2))");
890  qobj = qp.parse_query("title:(stuff) me", Xapian::QueryParser::FLAG_BOOLEAN);
891  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((ZXTstuff@1 OR ZAme@2))");
892 
893  qobj = qp.parse_query("hello world", 0, "B");
894  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((ZBhello@1 OR ZBworld@2))");
895  qobj = qp.parse_query("me title:stuff", 0, "B");
896  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((ZBme@1 OR ZXTstuff@2))");
897  qobj = qp.parse_query("title:(stuff) me", Xapian::QueryParser::FLAG_BOOLEAN, "B");
898  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((ZXTstuff@1 OR ZBme@2))");
899 
900  qp.add_prefix("", "B");
901  qobj = qp.parse_query("me-us title:(stuff) me", Xapian::QueryParser::FLAG_BOOLEAN);
902  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)))");
903  qobj = qp.parse_query("me-us title:(stuff) me", Xapian::QueryParser::FLAG_BOOLEAN, "C");
904  TEST_STRINGS_EQUAL(qobj.get_description(), "Query(((Cme@1 PHRASE 2 Cus@2) OR ZXTstuff@3 OR ZCme@4))");
905 
906  qobj = qp.parse_query("me-us title:\"not-me\"", Xapian::QueryParser::FLAG_PHRASE);
907  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)))");
908 }
909 
910 // Test query with odd characters in.
911 DEFINE_TESTCASE(qp_odd_chars1, !backend) {
913  string query("\x01weird\x00stuff\x7f", 13);
914  Xapian::Query qobj = qp.parse_query(query);
915  tout << "Query: " << query << '\n';
916  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((weird@1 OR stuff@2))"); // FIXME: should these be stemmed?
917 }
918 
919 // Test right truncation.
920 DEFINE_TESTCASE(qp_flag_wildcard1, backend) {
921  Xapian::Database db = get_database("qp_flag_wildcard1",
922  [](Xapian::WritableDatabase& wdb,
923  const string&) {
924  Xapian::Document doc;
925  doc.add_term("abc");
926  doc.add_term("main");
927  doc.add_term("muscat");
928  doc.add_term("muscle");
929  doc.add_term("musclebound");
930  doc.add_term("muscular");
931  doc.add_term("mutton");
932  wdb.add_document(doc);
933  });
935  qp.set_database(db);
936  Xapian::Query qobj = qp.parse_query("ab*", Xapian::QueryParser::FLAG_WILDCARD);
937  TEST_STRINGS_EQUAL(qobj.get_description(), "Query(WILDCARD SYNONYM ab)");
938  qobj = qp.parse_query("muscle*", Xapian::QueryParser::FLAG_WILDCARD);
939  TEST_STRINGS_EQUAL(qobj.get_description(), "Query(WILDCARD SYNONYM muscle)");
940  qobj = qp.parse_query("meat*", Xapian::QueryParser::FLAG_WILDCARD);
941  TEST_STRINGS_EQUAL(qobj.get_description(), "Query(WILDCARD SYNONYM meat)");
942  qobj = qp.parse_query("musc*", Xapian::QueryParser::FLAG_WILDCARD);
943  TEST_STRINGS_EQUAL(qobj.get_description(), "Query(WILDCARD SYNONYM musc)");
944  qobj = qp.parse_query("mutt*", Xapian::QueryParser::FLAG_WILDCARD);
945  TEST_STRINGS_EQUAL(qobj.get_description(), "Query(WILDCARD SYNONYM mutt)");
946  // Regression test (we weren't lowercasing terms before checking if they
947  // were in the database or not):
948  qobj = qp.parse_query("mUTTON++");
949  TEST_STRINGS_EQUAL(qobj.get_description(), "Query(mutton@1)");
950  // Regression test: check that wildcards work with +terms.
951  unsigned flags = Xapian::QueryParser::FLAG_WILDCARD |
953  qobj = qp.parse_query("+mai* main", flags);
954  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM mai AND_MAYBE main@2))");
955  // Regression test (if we had a +term which was a wildcard and wasn't
956  // present, the query could still match documents).
957  qobj = qp.parse_query("foo* main", flags);
958  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM foo OR main@2))");
959  qobj = qp.parse_query("main foo*", flags);
960  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@1 OR WILDCARD SYNONYM foo))");
961  qobj = qp.parse_query("+foo* main", flags);
962  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM foo AND_MAYBE main@2))");
963  qobj = qp.parse_query("main +foo*", flags);
964  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM foo AND_MAYBE main@1))");
965  qobj = qp.parse_query("foo* +main", flags);
966  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@2 AND_MAYBE WILDCARD SYNONYM foo))");
967  qobj = qp.parse_query("+main foo*", flags);
968  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@1 AND_MAYBE WILDCARD SYNONYM foo))");
969  qobj = qp.parse_query("+foo* +main", flags);
970  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM foo AND main@2))");
971  qobj = qp.parse_query("+main +foo*", flags);
972  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@1 AND WILDCARD SYNONYM foo))");
973  qobj = qp.parse_query("foo* mai", flags);
974  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM foo OR mai@2))");
975  qobj = qp.parse_query("mai foo*", flags);
976  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((mai@1 OR WILDCARD SYNONYM foo))");
977  qobj = qp.parse_query("+foo* mai", flags);
978  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM foo AND_MAYBE mai@2))");
979  qobj = qp.parse_query("mai +foo*", flags);
980  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM foo AND_MAYBE mai@1))");
981  qobj = qp.parse_query("foo* +mai", flags);
982  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((mai@2 AND_MAYBE WILDCARD SYNONYM foo))");
983  qobj = qp.parse_query("+mai foo*", flags);
984  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((mai@1 AND_MAYBE WILDCARD SYNONYM foo))");
985  qobj = qp.parse_query("+foo* +mai", flags);
986  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM foo AND mai@2))");
987  qobj = qp.parse_query("+mai +foo*", flags);
988  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((mai@1 AND WILDCARD SYNONYM foo))");
989  qobj = qp.parse_query("-foo* main", flags);
990  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@2 AND_NOT WILDCARD SYNONYM foo))");
991  qobj = qp.parse_query("main -foo*", flags);
992  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@1 AND_NOT WILDCARD SYNONYM foo))");
993  qobj = qp.parse_query("main -foo* -bar", flags);
994  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@1 AND_NOT (WILDCARD SYNONYM foo OR bar@3)))");
995  qobj = qp.parse_query("main -bar -foo*", flags);
996  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@1 AND_NOT (bar@2 OR WILDCARD SYNONYM foo)))");
997  // Check with OP_AND too.
998  qp.set_default_op(Xapian::Query::OP_AND);
999  qobj = qp.parse_query("foo* main", flags);
1000  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM foo AND main@2))");
1001  qobj = qp.parse_query("main foo*", flags);
1002  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@1 AND WILDCARD SYNONYM foo))");
1003  qp.set_default_op(Xapian::Query::OP_AND);
1004  qobj = qp.parse_query("+foo* main", flags);
1005  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM foo AND main@2))");
1006  qobj = qp.parse_query("main +foo*", flags);
1007  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@1 AND WILDCARD SYNONYM foo))");
1008  qobj = qp.parse_query("-foo* main", flags);
1009  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@2 AND_NOT WILDCARD SYNONYM foo))");
1010  qobj = qp.parse_query("main -foo*", flags);
1011  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((main@1 AND_NOT WILDCARD SYNONYM foo))");
1012  // Check empty wildcard followed by negation.
1013  qobj = qp.parse_query("foo* -main", Xapian::QueryParser::FLAG_LOVEHATE|Xapian::QueryParser::FLAG_WILDCARD);
1014  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM foo AND_NOT main@2))");
1015  // Regression test for bug#484 fixed in 1.2.1 and 1.0.21.
1016  qobj = qp.parse_query("abc muscl* main", flags);
1017  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((abc@1 AND WILDCARD SYNONYM muscl AND main@3))");
1018 }
1019 
1020 // Test right truncation with prefixes.
1021 DEFINE_TESTCASE(qp_flag_wildcard2, backend) {
1022  Xapian::Database db = get_database("qp_flag_wildcard2",
1023  [](Xapian::WritableDatabase& wdb,
1024  const string&) {
1025  Xapian::Document doc;
1026  doc.add_term("Aheinlein");
1027  doc.add_term("Ahuxley");
1028  doc.add_term("hello");
1029  wdb.add_document(doc);
1030  });
1032  qp.set_database(db);
1033  qp.add_prefix("author", "A");
1034  Xapian::Query qobj;
1035  qobj = qp.parse_query("author:h*", Xapian::QueryParser::FLAG_WILDCARD);
1036  TEST_STRINGS_EQUAL(qobj.get_description(), "Query(WILDCARD SYNONYM Ah)");
1037  qobj = qp.parse_query("author:h* test", Xapian::QueryParser::FLAG_WILDCARD);
1038  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM Ah OR test@2))");
1039 }
1040 
1041 static void
1043  Xapian::termcount max_expansion,
1044  const string & query_string)
1045 {
1047  qp.set_database(db);
1048  qp.set_max_expansion(max_expansion);
1049  Xapian::Enquire e(db);
1051  // The exception for expanding too much may happen at parse time or later
1052  // so we need to calculate the MSet too.
1053  e.get_mset(0, 10);
1054 }
1055 
1056 // Test right truncation with a limit on expansion.
1057 DEFINE_TESTCASE(qp_flag_wildcard3, backend) {
1058  Xapian::Database db = get_database("qp_flag_wildcard3",
1059  [](Xapian::WritableDatabase& wdb,
1060  const string&) {
1061  Xapian::Document doc;
1062  doc.add_term("abc");
1063  doc.add_term("main");
1064  doc.add_term("muscat");
1065  doc.add_term("muscle");
1066  doc.add_term("musclebound");
1067  doc.add_term("muscular");
1068  doc.add_term("mutton");
1069  wdb.add_document(doc);
1070  });
1071 
1072  // Test that a max of 0 doesn't set a limit.
1073  test_qp_flag_wildcard3_helper(db, 0, "z*");
1074  test_qp_flag_wildcard3_helper(db, 0, "m*");
1075 
1076  // These cases should expand to the limit given.
1077  test_qp_flag_wildcard3_helper(db, 1, "z*");
1078  test_qp_flag_wildcard3_helper(db, 1, "ab*");
1079  test_qp_flag_wildcard3_helper(db, 2, "muscle*");
1080  test_qp_flag_wildcard3_helper(db, 4, "musc*");
1081  test_qp_flag_wildcard3_helper(db, 4, "mus*");
1082  test_qp_flag_wildcard3_helper(db, 5, "mu*");
1083  test_qp_flag_wildcard3_helper(db, 6, "m*");
1084 
1085  // These cases should expand to one more than the limit.
1087  test_qp_flag_wildcard3_helper(db, 1, "muscle*"));
1089  test_qp_flag_wildcard3_helper(db, 3, "musc*"));
1091  test_qp_flag_wildcard3_helper(db, 3, "mus*"));
1093  test_qp_flag_wildcard3_helper(db, 4, "mu*"));
1095  test_qp_flag_wildcard3_helper(db, 5, "m*"));
1096 }
1097 
1098 static void
1100  const string&)
1101 {
1102  Xapian::Document doc;
1103  Xapian::Stem stemmer("english");
1104  doc.add_term("abc");
1105  doc.add_term("main");
1106  doc.add_term("muscat");
1107  doc.add_term("muscle");
1108  doc.add_term("musclebound");
1109  doc.add_term("muscular");
1110  doc.add_term("mutton");
1111  doc.add_term("Z" + stemmer("outside"));
1112  doc.add_term("Z" + stemmer("out"));
1113  doc.add_term("outside");
1114  doc.add_term("out");
1115  doc.add_term("XTcove");
1116  doc.add_term("XTcows");
1117  doc.add_term("XTcowl");
1118  doc.add_term("XTcox");
1119  doc.add_term("ZXTcow");
1120  doc.add_term("XONEpartial");
1121  doc.add_term("XONEpartial2");
1122  doc.add_term("XTWOpartial3");
1123  doc.add_term("XTWOpartial4");
1124  db.add_document(doc);
1125 }
1126 
1127 // Test partial queries.
1128 DEFINE_TESTCASE(qp_flag_partial1, backend) {
1129  Xapian::Database db = get_database("qp_flag_partial1",
1131  Xapian::Stem stemmer("english");
1133  qp.set_database(db);
1134  qp.set_stemmer(stemmer);
1136  qp.add_prefix("title", "XT");
1137  qp.add_prefix("double", "XONE");
1138  qp.add_prefix("double", "XTWO");
1139 
1140  // Check behaviour with unstemmed terms
1142  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM a OR Za@1))");
1144  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM ab OR Zab@1))");
1145  qobj = qp.parse_query("muscle", Xapian::QueryParser::FLAG_PARTIAL);
1146  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM muscle OR Zmuscl@1))");
1148  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM meat OR Zmeat@1))");
1150  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM musc OR Zmusc@1))");
1152  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM mutt OR Zmutt@1))");
1153  qobj = qp.parse_query("abc musc", Xapian::QueryParser::FLAG_PARTIAL);
1154  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((Zabc@1 OR (WILDCARD SYNONYM musc OR Zmusc@2)))");
1156  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM a OR (WILDCARD SYNONYM mutt OR Zmutt@2)))");
1157 
1158  // Check behaviour with stemmed terms, and stem strategy STEM_SOME.
1160  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM o OR Zo@1))");
1162  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM ou OR Zou@1))");
1164  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM out OR Zout@1))");
1166  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outs OR Zout@1))");
1167  qobj = qp.parse_query("outsi", Xapian::QueryParser::FLAG_PARTIAL);
1168  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outsi OR Zoutsi@1))");
1169  qobj = qp.parse_query("outsid", Xapian::QueryParser::FLAG_PARTIAL);
1170  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outsid OR Zoutsid@1))");
1171  qobj = qp.parse_query("outside", Xapian::QueryParser::FLAG_PARTIAL);
1172  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outside OR Zoutsid@1))");
1173 
1174  // Check behaviour with capitalised terms, and stem strategy STEM_SOME.
1176  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM out OR out@1))");
1178  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outs OR outs@1))");
1179  qobj = qp.parse_query("Outside", Xapian::QueryParser::FLAG_PARTIAL);
1180  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outside OR outside@1))");
1181  // FIXME: Used to be this, but we aren't currently doing this change:
1182  // TEST_STRINGS_EQUAL(qobj.get_description(), "Query(outside@1#2)");
1183 
1184  // And now with stemming strategy STEM_SOME_FULL_POS.
1187  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM out OR out@1))");
1189  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outs OR outs@1))");
1190  qobj = qp.parse_query("Outside", Xapian::QueryParser::FLAG_PARTIAL);
1191  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outside OR outside@1))");
1192 
1193  // And now with stemming strategy STEM_ALL.
1196  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM out OR out@1))");
1198  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outs OR out@1))");
1199  qobj = qp.parse_query("Outside", Xapian::QueryParser::FLAG_PARTIAL);
1200  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outside OR outsid@1))");
1201 
1202  // And now with stemming strategy STEM_ALL_Z.
1205  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM out OR Zout@1))");
1207  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outs OR Zout@1))");
1208  qobj = qp.parse_query("Outside", Xapian::QueryParser::FLAG_PARTIAL);
1209  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM outside OR Zoutsid@1))");
1210 
1211  // Check handling of a case with a prefix.
1213  qobj = qp.parse_query("title:cow", Xapian::QueryParser::FLAG_PARTIAL);
1214  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM XTcow OR ZXTcow@1))");
1215  qobj = qp.parse_query("title:cows", Xapian::QueryParser::FLAG_PARTIAL);
1216  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM XTcows OR ZXTcow@1))");
1217  qobj = qp.parse_query("title:Cow", Xapian::QueryParser::FLAG_PARTIAL);
1218  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM XTcow OR XTcow@1))");
1219  qobj = qp.parse_query("title:Cows", Xapian::QueryParser::FLAG_PARTIAL);
1220  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM XTcows OR XTcows@1))");
1221  // FIXME: Used to be this, but we aren't currently doing this change:
1222  // TEST_STRINGS_EQUAL(qobj.get_description(), "Query(XTcows@1#2)");
1223 
1224  // Regression test - the initial version of the multi-prefix code would
1225  // inflate the wqf of the "parsed as normal" version of a partial term
1226  // by multiplying it by the number of prefixes mapped to.
1227  qobj = qp.parse_query("double:vision", Xapian::QueryParser::FLAG_PARTIAL);
1228  TEST_STRINGS_EQUAL(qobj.get_description(), "Query(((WILDCARD OR XONEvision SYNONYM WILDCARD OR XTWOvision) OR (ZXONEvision@1 SYNONYM ZXTWOvision@1)))");
1229 
1230  // Test handling of FLAG_PARTIAL when there's more than one prefix.
1231  qobj = qp.parse_query("double:part", Xapian::QueryParser::FLAG_PARTIAL);
1232  TEST_STRINGS_EQUAL(qobj.get_description(), "Query(((WILDCARD OR XONEpart SYNONYM WILDCARD OR XTWOpart) OR (ZXONEpart@1 SYNONYM ZXTWOpart@1)))");
1233 
1234  // Test handling of FLAG_PARTIAL when there's more than one prefix, without
1235  // stemming.
1237  qobj = qp.parse_query("double:part", Xapian::QueryParser::FLAG_PARTIAL);
1238  TEST_STRINGS_EQUAL(qobj.get_description(), "Query(((WILDCARD OR XONEpart SYNONYM WILDCARD OR XTWOpart) OR (XONEpart@1 SYNONYM XTWOpart@1)))");
1239  qobj = qp.parse_query("double:partial", Xapian::QueryParser::FLAG_PARTIAL);
1240  TEST_STRINGS_EQUAL(qobj.get_description(), "Query(((WILDCARD OR XONEpartial SYNONYM WILDCARD OR XTWOpartial) OR (XONEpartial@1 SYNONYM XTWOpartial@1)))");
1241 
1242  // Check that the partial term isn't included if it's a stopword. Feature
1243  // test for behavioural change in 1.4.31.
1244  static const char * const stopwords[] = { "a", "an", "the" };
1245  Xapian::SimpleStopper stop(stopwords, stopwords + 3);
1246  qp.set_stopper(&stop);
1249  TEST_STRINGS_EQUAL(qobj.get_description(), "Query(WILDCARD SYNONYM a)");
1251  TEST_STRINGS_EQUAL(qobj.get_description(), "Query(WILDCARD SYNONYM an)");
1253  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM ant OR Zant@1))");
1255  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM th OR Zth@1))");
1257  TEST_STRINGS_EQUAL(qobj.get_description(), "Query(WILDCARD SYNONYM the)");
1259  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((WILDCARD SYNONYM theo OR Ztheo@1))");
1260 }
1261 
1262 // Tests for document counts for wildcard queries.
1263 // Regression test for bug fixed in 1.0.0.
1264 DEFINE_TESTCASE(wildquery1, backend) {
1265  Xapian::QueryParser queryparser;
1266  unsigned flags = Xapian::QueryParser::FLAG_WILDCARD |
1268  queryparser.set_stemmer(Xapian::Stem("english"));
1270  Xapian::Database db = get_database("apitest_simpledata");
1271  queryparser.set_database(db);
1272  Xapian::Enquire enquire(db);
1273 
1274  Xapian::Query qobj = queryparser.parse_query("th*", flags);
1275  tout << qobj.get_description() << '\n';
1276  enquire.set_query(qobj);
1277  Xapian::MSet mymset = enquire.get_mset(0, 10);
1278  // Check that 6 documents were returned.
1279  TEST_MSET_SIZE(mymset, 6);
1280 
1281  qobj = queryparser.parse_query("notindb* \"this\"", flags);
1282  tout << qobj.get_description() << '\n';
1283  enquire.set_query(qobj);
1284  mymset = enquire.get_mset(0, 10);
1285  // Check that 6 documents were returned.
1286  TEST_MSET_SIZE(mymset, 6);
1287 
1288  qobj = queryparser.parse_query("+notindb* \"this\"", flags);
1289  tout << qobj.get_description() << '\n';
1290  enquire.set_query(qobj);
1291  mymset = enquire.get_mset(0, 10);
1292  // Check that 0 documents were returned.
1293  TEST_MSET_SIZE(mymset, 0);
1294 }
1295 
1296 DEFINE_TESTCASE(qp_flag_bool_any_case1, !backend) {
1297  using Xapian::QueryParser;
1299  Xapian::Query qobj;
1300  qobj = qp.parse_query("to and fro", QueryParser::FLAG_BOOLEAN | QueryParser::FLAG_BOOLEAN_ANY_CASE);
1301  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((to@1 AND fro@2))");
1302  qobj = qp.parse_query("to and fro", QueryParser::FLAG_BOOLEAN);
1303  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((to@1 OR and@2 OR fro@3))");
1304  // Regression test for bug in 0.9.4 and earlier.
1305  qobj = qp.parse_query("to And fro", QueryParser::FLAG_BOOLEAN | QueryParser::FLAG_BOOLEAN_ANY_CASE);
1306  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((to@1 AND fro@2))");
1307  qobj = qp.parse_query("to And fro", QueryParser::FLAG_BOOLEAN);
1308  TEST_STRINGS_EQUAL(qobj.get_description(), "Query((to@1 OR and@2 OR fro@3))");
1309 }
1310 
1311 static const test test_stop_queries[] = {
1312  { "test the queryparser", "(test@1 AND queryparser@3)" },
1313  // Regression test for bug in 0.9.6 and earlier. This would fail to
1314  // parse.
1315  { "test AND the AND queryparser", "(test@1 AND the@2 AND queryparser@3)" },
1316  // 0.9.6 and earlier ignored a stopword even if it was the only term.
1317  // More recent versions don't ever treat a single term as a stopword.
1318  { "the", "the@1" },
1319  // 1.2.2 and earlier ignored an all-stopword query with multiple terms,
1320  // which prevents 'to be or not to be' for being searchable unless the
1321  // user made it into a phrase query or prefixed all terms with '+'
1322  // (ticket#245).
1323  { "an the a", "(an@1 AND the@2 AND a@3)" },
1324  // Regression test for bug in initial version of the patch for the
1325  // "all-stopword" case.
1326  { "the AND a an", "(the@1 AND (a@2 AND an@3))" },
1327  { NULL, NULL }
1328 };
1329 
1330 DEFINE_TESTCASE(qp_stopper1, !backend) {
1332  static const char * const stopwords[] = { "a", "an", "the" };
1333  Xapian::SimpleStopper stop(stopwords, stopwords + 3);
1334  qp.set_stopper(&stop);
1336  for (const test *p = test_stop_queries; p->query; ++p) {
1337  string expect, parsed;
1338  if (p->expect)
1339  expect = p->expect;
1340  else
1341  expect = "parse error";
1342  try {
1343  Xapian::Query qobj = qp.parse_query(p->query);
1344  parsed = qobj.get_description();
1345  expect = string("Query(") + expect + ')';
1346  } catch (const Xapian::QueryParserError &e) {
1347  parsed = e.get_msg();
1348  } catch (const Xapian::Error &e) {
1349  parsed = e.get_description();
1350  } catch (...) {
1351  parsed = "Unknown exception!";
1352  }
1353  tout << "Query: " << p->query << '\n';
1354  TEST_STRINGS_EQUAL(parsed, expect);
1355  }
1356 }
1357 
1358 static const test test_pure_not_queries[] = {
1359  { "NOT windows", "(<alldocuments> AND_NOT Zwindow@1)" },
1360  { "a AND (NOT b)", "(Za@1 AND (<alldocuments> AND_NOT Zb@2))" },
1361  { "AND NOT windows", "Syntax: <expression> AND NOT <expression>" },
1362  { "gordian NOT", "Syntax: <expression> NOT <expression>" },
1363  { "gordian AND NOT", "Syntax: <expression> AND NOT <expression>" },
1364  { NULL, NULL }
1365 };
1366 
1367 DEFINE_TESTCASE(qp_flag_pure_not1, !backend) {
1368  using Xapian::QueryParser;
1370  qp.set_stemmer(Xapian::Stem("english"));
1371  qp.set_stemming_strategy(QueryParser::STEM_SOME);
1372  for (const test *p = test_pure_not_queries; p->query; ++p) {
1373  string expect, parsed;
1374  if (p->expect)
1375  expect = p->expect;
1376  else
1377  expect = "parse error";
1378  try {
1379  Xapian::Query qobj = qp.parse_query(p->query,
1380  QueryParser::FLAG_BOOLEAN |
1381  QueryParser::FLAG_PURE_NOT);
1382  parsed = qobj.get_description();
1383  expect = string("Query(") + expect + ')';
1384  } catch (const Xapian::QueryParserError &e) {
1385  parsed = e.get_msg();
1386  } catch (const Xapian::Error &e) {
1387  parsed = e.get_description();
1388  } catch (...) {
1389  parsed = "Unknown exception!";
1390  }
1391  tout << "Query: " << p->query << '\n';
1392  TEST_STRINGS_EQUAL(parsed, expect);
1393  }
1394 }
1395 
1396 // Debatable if this is a regression test or a feature test, as it's not
1397 // obvious is this was a bug fix or a new feature. Either way, it first
1398 // appeared in Xapian 1.0.0.
1399 DEFINE_TESTCASE(qp_unstem_boolean_prefix, !backend) {
1401  qp.add_boolean_prefix("test", "XTEST");
1402  Xapian::Query q = qp.parse_query("hello test:foo");
1403  TEST_STRINGS_EQUAL(q.get_description(), "Query((hello@1 FILTER XTESTfoo))");
1404  Xapian::TermIterator u = qp.unstem_begin("XTESTfoo");
1405  TEST(u != qp.unstem_end("XTESTfoo"));
1406  TEST_EQUAL(*u, "test:foo");
1407  ++u;
1408  TEST(u == qp.unstem_end("XTESTfoo"));
1409 }
1410 
1411 // Feature test for FLAG_ACCUMULATE.
1412 DEFINE_TESTCASE(qp_accumulate, !backend) {
1414  Xapian::SimpleStopper stopper;
1415  stopper.add("a");
1416  stopper.add("the");
1417  qp.set_stopper(&stopper);
1418  qp.set_stemmer(Xapian::Stem("en"));
1419  qp.add_boolean_prefix("test", "XTEST");
1420  qp.add_prefix("foo", "XFOO");
1421  Xapian::Query q = qp.parse_query("a plains test:bools foo:fielded");
1422  tout << q.get_description() << '\n';
1423  {
1424  Xapian::TermIterator t = qp.unstem_begin("Zplain");
1425  TEST(t != qp.unstem_end("Zplain"));
1426  TEST_EQUAL(*t, "plains");
1427  ++t;
1428  TEST(t == qp.unstem_end("Zplain"));
1429  }
1430  {
1431  Xapian::TermIterator t = qp.unstem_begin("XTESTbools");
1432  TEST(t != qp.unstem_end("XTESTbools"));
1433  TEST_EQUAL(*t, "test:bools");
1434  ++t;
1435  TEST(t == qp.unstem_end("XTESTbools"));
1436  }
1437  {
1438  Xapian::TermIterator t = qp.unstem_begin("ZXFOOfield");
1439  TEST(t != qp.unstem_end("ZXFOOfield"));
1440  TEST_EQUAL(*t, "fielded");
1441  ++t;
1442  TEST(t == qp.unstem_end("ZXFOOfield"));
1443  }
1444  {
1446  TEST(t != qp.stoplist_end());
1447  TEST_EQUAL(*t, "a");
1448  ++t;
1449  TEST(t == qp.stoplist_end());
1450  }
1451  q = qp.parse_query("the plain foo:fields",
1452  qp.FLAG_DEFAULT | qp.FLAG_ACCUMULATE);
1453  tout << q.get_description() << '\n';
1454  {
1455  Xapian::TermIterator t = qp.unstem_begin("Zplain");
1456  TEST(t != qp.unstem_end("Zplain"));
1457  TEST_EQUAL(*t, "plains");
1458  ++t;
1459  TEST(t != qp.unstem_end("Zplain"));
1460  TEST_EQUAL(*t, "plain");
1461  ++t;
1462  TEST(t == qp.unstem_end("Zplain"));
1463  }
1464  {
1465  Xapian::TermIterator t = qp.unstem_begin("XTESTbools");
1466  TEST(t != qp.unstem_end("XTESTbools"));
1467  TEST_EQUAL(*t, "test:bools");
1468  ++t;
1469  TEST(t == qp.unstem_end("XTESTbools"));
1470  }
1471  {
1472  Xapian::TermIterator t = qp.unstem_begin("ZXFOOfield");
1473  TEST(t != qp.unstem_end("ZXFOOfield"));
1474  TEST_EQUAL(*t, "fielded");
1475  ++t;
1476  TEST(t != qp.unstem_end("ZXFOOfield"));
1477  TEST_EQUAL(*t, "fields");
1478  ++t;
1479  TEST(t == qp.unstem_end("ZXFOOfield"));
1480  }
1481  {
1483  TEST(t != qp.stoplist_end());
1484  TEST_EQUAL(*t, "a");
1485  ++t;
1486  TEST(t != qp.stoplist_end());
1487  TEST_EQUAL(*t, "the");
1488  ++t;
1489  TEST(t == qp.stoplist_end());
1490  }
1491  // Check things are reset without FLAG_ACCUMULATE.
1492  q = qp.parse_query("plains");
1493  tout << q.get_description() << '\n';
1494  {
1495  Xapian::TermIterator t = qp.unstem_begin("Zplain");
1496  TEST(t != qp.unstem_end("Zplain"));
1497  TEST_EQUAL(*t, "plains");
1498  ++t;
1499  TEST(t == qp.unstem_end("Zplain"));
1500  }
1501  {
1502  Xapian::TermIterator t = qp.unstem_begin("XTESTboolean");
1503  TEST(t == qp.unstem_end("XTESTboolean"));
1504  }
1505  {
1506  Xapian::TermIterator t = qp.unstem_begin("ZXFOOfield");
1507  TEST(t == qp.unstem_end("ZXFOOfield"));
1508  }
1509  {
1511  TEST(t == qp.stoplist_end());
1512  }
1513 }
1514 
1515 static const test test_value_range1_queries[] = {
1516  { "a..b", "VALUE_RANGE 1 a b" },
1517  { "$50..100", "VALUE_RANGE 1 $50 100" },
1518  { "$50..$99", "VALUE_RANGE 1 $50 $99" },
1519  { "$50..$100", "" }, // start > range
1520  { "02/03/1979..10/12/1980", "VALUE_RANGE 1 02/03/1979 10/12/1980" },
1521  { "a..b hello", "(hello@1 FILTER VALUE_RANGE 1 a b)" },
1522  { "hello a..b", "(hello@1 FILTER VALUE_RANGE 1 a b)" },
1523  { "hello a..b world", "((hello@1 OR world@2) FILTER VALUE_RANGE 1 a b)" },
1524  { "hello a..b test:foo", "(hello@1 FILTER (VALUE_RANGE 1 a b AND XTESTfoo))" },
1525  { "hello a..b test:foo test:bar", "(hello@1 FILTER (VALUE_RANGE 1 a b AND (XTESTfoo OR XTESTbar)))" },
1526  { "hello a..b c..d test:foo", "(hello@1 FILTER ((VALUE_RANGE 1 a b OR VALUE_RANGE 1 c d) AND XTESTfoo))" },
1527  { "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)))" },
1528  { "-5..7", "VALUE_RANGE 1 -5 7" },
1529  { "hello -5..7", "(hello@1 FILTER VALUE_RANGE 1 -5 7)" },
1530  { "-5..7 hello", "(hello@1 FILTER VALUE_RANGE 1 -5 7)" },
1531  { "\"time flies\" 09:00..12:30", "((time@1 PHRASE 2 flies@2) FILTER VALUE_RANGE 1 09:00 12:30)" },
1532  // Feature test for single-ended ranges (ticket#480):
1533  { "..b", "VALUE_LE 1 b" },
1534  { "a..", "VALUE_GE 1 a" },
1535  // Test for expanded set of characters allowed in range start:
1536  { "10:30+1300..11:00+1300", "VALUE_RANGE 1 10:30+1300 11:00+1300" },
1537  { NULL, NULL }
1538 };
1539 
1540 // Simple test of ValueRangeProcessor class.
1541 DEFINE_TESTCASE(qp_value_range1, !backend) {
1543  qp.add_boolean_prefix("test", "XTEST");
1545  qp.add_valuerangeprocessor(&vrp);
1546  for (const test *p = test_value_range1_queries; p->query; ++p) {
1547  string expect, parsed;
1548  if (p->expect)
1549  expect = p->expect;
1550  else
1551  expect = "parse error";
1552  try {
1553  Xapian::Query qobj = qp.parse_query(p->query);
1554  parsed = qobj.get_description();
1555  expect = string("Query(") + expect + ')';
1556  } catch (const Xapian::QueryParserError &e) {
1557  parsed = e.get_msg();
1558  } catch (const Xapian::Error &e) {
1559  parsed = e.get_description();
1560  } catch (...) {
1561  parsed = "Unknown exception!";
1562  }
1563  tout << "Query: " << p->query << '\n';
1564  TEST_STRINGS_EQUAL(parsed, expect);
1565  }
1566 }
1567 
1568 // Simple test of RangeProcessor class.
1569 DEFINE_TESTCASE(qp_range1, !backend) {
1571  qp.add_boolean_prefix("test", "XTEST");
1572  Xapian::RangeProcessor rp(1);
1573  qp.add_rangeprocessor(&rp);
1574  for (const test *p = test_value_range1_queries; p->query; ++p) {
1575  string expect, parsed;
1576  if (p->expect)
1577  expect = p->expect;
1578  else
1579  expect = "parse error";
1580  try {
1581  Xapian::Query qobj = qp.parse_query(p->query);
1582  parsed = qobj.get_description();
1583  expect = string("Query(") + expect + ')';
1584  } catch (const Xapian::QueryParserError &e) {
1585  parsed = e.get_msg();
1586  } catch (const Xapian::Error &e) {
1587  parsed = e.get_description();
1588  } catch (...) {
1589  parsed = "Unknown exception!";
1590  }
1591  tout << "Query: " << p->query << '\n';
1592  TEST_STRINGS_EQUAL(parsed, expect);
1593  }
1594 }
1595 
1596 static const test test_value_range2_queries[] = {
1597  { "a..b", "VALUE_RANGE 3 a b" },
1598  { "1..12", "VALUE_RANGE 2 \\xa0 \\xae" },
1599  { "20070201..20070228", "VALUE_RANGE 1 20070201 20070228" },
1600  { "$10..20", "VALUE_RANGE 4 \\xad \\xb1" },
1601  { "$10..$20", "VALUE_RANGE 4 \\xad \\xb1" },
1602  // Feature test for single-ended ranges (ticket#480):
1603  { "$..20", "VALUE_LE 4 \\xb1" },
1604  { "..$20", "VALUE_LE 4 \\xb1" },
1605  { "$10..", "VALUE_GE 4 \\xad" },
1606  { "12..42kg", "VALUE_RANGE 5 \\xae \\xb5@" },
1607  { "12kg..42kg", "VALUE_RANGE 5 \\xae \\xb5@" },
1608  { "12kg..42", "VALUE_RANGE 3 12kg 42" },
1609  { "..42kg", "VALUE_LE 5 \\xb5@" },
1610  { "kg..42kg", "VALUE_LE 5 \\xb5@" },
1611  { "12..kg", "VALUE_GE 5 \\xae" },
1612  { "12kg..", "VALUE_GE 5 \\xae" },
1613  { "1..5!", "VALUE_RANGE 6 \\xa0 \\xa9" },
1614  { "..5!", "VALUE_LE 6 \\xa9" },
1615  { "1..!", "VALUE_GE 6 \\xa0" },
1616  { "10..$20", "" }, // start > end
1617  { "kg..42", "" }, // start > end
1618  { "1999-03-12..2020-12-30", "VALUE_RANGE 1 19990312 20201230" },
1619  { "1999/03/12..2020/12/30", "VALUE_RANGE 1 19990312 20201230" },
1620  { "1999.03.12..2020.12.30", "VALUE_RANGE 1 19990312 20201230" },
1621  { "date:1999-03-12..2020-12-30", "VALUE_RANGE 1 19990312 20201230" },
1622  // Feature test for single-ended ranges (ticket#480):
1623  { "..2020.12.30", "VALUE_LE 1 20201230" },
1624  { "1999.03.12..", "VALUE_GE 1 19990312" },
1625  { "date:..2020.12.30", "VALUE_LE 1 20201230" },
1626  { "date:1999.03.12..", "VALUE_GE 1 19990312" },
1627  { "1999.03.12..date:", "VALUE_RANGE 3 1999.03.12 date:" },
1628  { "12/03/99..12/04/01", "VALUE_RANGE 1 19990312 20010412" },
1629  { "03-12-99..04-14-01", "VALUE_RANGE 1 19990312 20010414" },
1630  { "1/2/3..2/3/4", "VALUE_RANGE 1 20030201 20040302" },
1631  { "(test:a..test:b hello)", "(hello@1 FILTER VALUE_RANGE 3 test:a test:b)" },
1632  { "12..42kg 5..6kg 1..12", "0 * (VALUE_RANGE 2 \\xa0 \\xae AND (VALUE_RANGE 5 \\xae \\xb5@ OR VALUE_RANGE 5 \\xa9 \\xaa))" },
1633  // Check that a VRP which fails to match doesn't remove a prefix or suffix.
1634  // 1.0.13/1.1.1 and earlier got this wrong in some cases.
1635  { "$12a..13", "VALUE_RANGE 3 $12a 13" },
1636  { "$12..13b", "VALUE_RANGE 3 $12 13b" },
1637  { "$12..12kg", "VALUE_RANGE 3 $12 12kg" },
1638  { "12..b12kg", "VALUE_RANGE 3 12 b12kg" },
1639  { NULL, "" },
1640  // Extra test-cases for RangeProcessor only:
1641  { "..date:2020.12.30", "VALUE_LE 3 date:2020.12.30" },
1642  // Test repeating without RP_REPEATED.
1643  { "date:2000-01-01..date:2001-01-01", "VALUE_RANGE 3 date:2000-01-01 date:2001-01-01" },
1644  { "1!..5!", "VALUE_RANGE 3 1! 5!" },
1645  { NULL, NULL }
1646 };
1647 
1648 // Test chaining of ValueRangeProcessor classes.
1649 DEFINE_TESTCASE(qp_value_range2, !backend) {
1651  qp.add_boolean_prefix("test", "XTEST");
1652  Xapian::DateValueRangeProcessor vrp_date(1);
1653  Xapian::DateValueRangeProcessor vrp_date2(1, "date:");
1656  Xapian::NumberValueRangeProcessor vrp_cash(4, "$");
1657  Xapian::NumberValueRangeProcessor vrp_weight(5, "kg", false);
1658  Xapian::NumberValueRangeProcessor vrp_suffix(6, "!", false);
1659  qp.add_valuerangeprocessor(&vrp_date);
1660  qp.add_valuerangeprocessor(&vrp_date2);
1661  qp.add_valuerangeprocessor(&vrp_num);
1662  qp.add_valuerangeprocessor(&vrp_cash);
1663  qp.add_valuerangeprocessor(&vrp_weight);
1664  qp.add_valuerangeprocessor(&vrp_suffix);
1665  qp.add_valuerangeprocessor(&vrp_str);
1666  for (const test *p = test_value_range2_queries; p->query; ++p) {
1667  string expect, parsed;
1668  if (p->expect)
1669  expect = p->expect;
1670  else
1671  expect = "parse error";
1672  try {
1673  Xapian::Query qobj = qp.parse_query(p->query);
1674  parsed = qobj.get_description();
1675  expect = string("Query(") + expect + ')';
1676  } catch (const Xapian::QueryParserError &e) {
1677  parsed = e.get_msg();
1678  } catch (const Xapian::Error &e) {
1679  parsed = e.get_description();
1680  } catch (...) {
1681  parsed = "Unknown exception!";
1682  }
1683  tout << "Query: " << p->query << '\n';
1684  TEST_STRINGS_EQUAL(parsed, expect);
1685  }
1686 }
1687 
1688 // Test chaining of RangeProcessor classes.
1689 DEFINE_TESTCASE(qp_range2, !backend) {
1690  using Xapian::RP_REPEATED;
1691  using Xapian::RP_SUFFIX;
1693  qp.add_boolean_prefix("test", "XTEST");
1694  Xapian::DateRangeProcessor rp_date(1);
1695  Xapian::DateRangeProcessor rp_date2(1, "date:");
1696  Xapian::NumberRangeProcessor rp_num(2);
1697  Xapian::RangeProcessor rp_str(3);
1698  Xapian::NumberRangeProcessor rp_cash(4, "$", RP_REPEATED);
1700  Xapian::NumberRangeProcessor rp_suffix(6, "!", RP_SUFFIX);
1701  qp.add_rangeprocessor(&rp_date);
1702  qp.add_rangeprocessor(&rp_date2);
1703  qp.add_rangeprocessor(&rp_num);
1704  qp.add_rangeprocessor(&rp_cash);
1705  qp.add_rangeprocessor(&rp_weight);
1706  qp.add_rangeprocessor(&rp_suffix);
1707  qp.add_rangeprocessor(&rp_str);
1708  for (const test *p = test_value_range2_queries;
1709  p->query || p->expect; ++p) {
1710  // Continue on to extra testcases for RangeProcessor only.
1711  if (!p->query) continue;
1712 
1713  string expect, parsed;
1714  if (p->expect)
1715  expect = p->expect;
1716  else
1717  expect = "parse error";
1718  try {
1719  Xapian::Query qobj = qp.parse_query(p->query);
1720  parsed = qobj.get_description();
1721  expect = string("Query(") + expect + ')';
1722  } catch (const Xapian::QueryParserError &e) {
1723  parsed = e.get_msg();
1724  } catch (const Xapian::Error &e) {
1725  parsed = e.get_description();
1726  } catch (...) {
1727  parsed = "Unknown exception!";
1728  }
1729  tout << "Query: " << p->query << '\n';
1730  TEST_STRINGS_EQUAL(parsed, expect);
1731  }
1732 }
1733 
1734 static void
1736 {
1737  double low = -10;
1738  int steps = 60;
1739  double step = 0.5;
1740 
1741  for (int i = 0; i <= steps; ++i) {
1742  double v = low + i * step;
1743  Xapian::Document doc;
1745  db.add_document(doc);
1746  }
1747 }
1748 
1749 // Test NumberValueRangeProcessors with actual data.
1750 DEFINE_TESTCASE(qp_value_range3, backend) {
1751  double low = -10;
1752  int steps = 60;
1753  double step = 0.5;
1754 
1755  Xapian::Database db = get_database("qp_range3", gen_qp_range3_db);
1756 
1759  qp.add_valuerangeprocessor(&vrp_num);
1760 
1761  for (int j = 0; j <= steps; ++j) {
1762  double start = low + j * step;
1763  for (int k = 0; k <= steps; ++k) {
1764  double end = low + k * step;
1765  string query = str(start) + ".." + str(end);
1766  tout << "Query: " << query << '\n';
1767  Xapian::Query qobj = qp.parse_query(query);
1768  Xapian::Enquire enq(db);
1769  enq.set_query(qobj);
1770  Xapian::MSet mset = enq.get_mset(0, steps + 1);
1771  if (end < start) {
1772  TEST_EQUAL(mset.size(), 0);
1773  } else {
1774  TEST_EQUAL(mset.size(), 1u + (k - j));
1775  for (unsigned int m = 0; m != mset.size(); ++m) {
1776  double v = start + m * step;
1777  TEST_EQUAL(mset[m].get_document().get_value(1),
1779  }
1780  }
1781  }
1782  }
1783 }
1784 
1785 // Test NumberRangeProcessors with actual data.
1786 DEFINE_TESTCASE(qp_range3, backend) {
1787  double low = -10;
1788  int steps = 60;
1789  double step = 0.5;
1790 
1791  Xapian::Database db = get_database("qp_range3", gen_qp_range3_db);
1792 
1793  Xapian::NumberRangeProcessor rp_num(1);
1795  qp.add_rangeprocessor(&rp_num);
1796 
1797  for (int j = 0; j <= steps; ++j) {
1798  double start = low + j * step;
1799  for (int k = 0; k <= steps; ++k) {
1800  double end = low + k * step;
1801  string query = str(start) + ".." + str(end);
1802  tout << "Query: " << query << '\n';
1803  Xapian::Query qobj = qp.parse_query(query);
1804  Xapian::Enquire enq(db);
1805  enq.set_query(qobj);
1806  Xapian::MSet mset = enq.get_mset(0, steps + 1);
1807  if (end < start) {
1808  TEST_EQUAL(mset.size(), 0);
1809  } else {
1810  TEST_EQUAL(mset.size(), 1u + (k - j));
1811  for (unsigned int m = 0; m != mset.size(); ++m) {
1812  double v = start + m * step;
1813  TEST_EQUAL(mset[m].get_document().get_value(1),
1815  }
1816  }
1817  }
1818  }
1819 }
1820 
1821 static const test test_value_range4_queries[] = {
1822  { "id:19254@foo..example.com", "0 * Q19254@foo..example.com" },
1823  { "hello:world", "0 * XHELLOworld" },
1824  { "hello:mum..world", "VALUE_RANGE 1 mum world" },
1825  { NULL, NULL }
1826 };
1827 
1834 DEFINE_TESTCASE(qp_value_range4, !backend) {
1836  qp.add_boolean_prefix("id", "Q");
1837  qp.add_boolean_prefix("hello", "XHELLO");
1838  Xapian::StringValueRangeProcessor vrp_str(1, "hello:");
1839  qp.add_valuerangeprocessor(&vrp_str);
1840  for (const test *p = test_value_range4_queries; p->query; ++p) {
1841  string expect, parsed;
1842  if (p->expect)
1843  expect = p->expect;
1844  else
1845  expect = "parse error";
1846  try {
1847  Xapian::Query qobj = qp.parse_query(p->query);
1848  parsed = qobj.get_description();
1849  expect = string("Query(") + expect + ')';
1850  } catch (const Xapian::QueryParserError &e) {
1851  parsed = e.get_msg();
1852  } catch (const Xapian::Error &e) {
1853  parsed = e.get_description();
1854  } catch (...) {
1855  parsed = "Unknown exception!";
1856  }
1857  tout << "Query: " << p->query << '\n';
1858  TEST_STRINGS_EQUAL(parsed, expect);
1859  }
1860 }
1861 
1868 DEFINE_TESTCASE(qp_range4, !backend) {
1870  qp.add_boolean_prefix("id", "Q");
1871  qp.add_boolean_prefix("hello", "XHELLO");
1872  Xapian::RangeProcessor rp_str(1, "hello:");
1873  qp.add_rangeprocessor(&rp_str);
1874  for (const test *p = test_value_range4_queries; p->query; ++p) {
1875  string expect, parsed;
1876  if (p->expect)
1877  expect = p->expect;
1878  else
1879  expect = "parse error";
1880  try {
1881  Xapian::Query qobj = qp.parse_query(p->query);
1882  parsed = qobj.get_description();
1883  expect = string("Query(") + expect + ')';
1884  } catch (const Xapian::QueryParserError &e) {
1885  parsed = e.get_msg();
1886  } catch (const Xapian::Error &e) {
1887  parsed = e.get_description();
1888  } catch (...) {
1889  parsed = "Unknown exception!";
1890  }
1891  tout << "Query: " << p->query << '\n';
1892  TEST_STRINGS_EQUAL(parsed, expect);
1893  }
1894 }
1895 
1896 static const test test_value_daterange1_queries[] = {
1897  { "12/03/99..12/04/01", "VALUE_RANGE 1 19991203 20011204" },
1898  { "03-12-99..04-14-01", "VALUE_RANGE 1 19990312 20010414" },
1899  { "01/30/60..02/02/59", "VALUE_RANGE 1 19600130 20590202" },
1900  { "1999-03-12..2001-04-14", "VALUE_RANGE 1 19990312 20010414" },
1901  { "12/03/99..02", "Unknown range operation" },
1902  { "1999-03-12..2001", "Unknown range operation" },
1903  { NULL, NULL }
1904 };
1905 
1906 // Test DateValueRangeProcessor
1907 DEFINE_TESTCASE(qp_value_daterange1, !backend) {
1909  Xapian::DateValueRangeProcessor vrp_date(1, true, 1960);
1910  qp.add_valuerangeprocessor(&vrp_date);
1911  for (const test *p = test_value_daterange1_queries; p->query; ++p) {
1912  string expect, parsed;
1913  if (p->expect)
1914  expect = p->expect;
1915  else
1916  expect = "parse error";
1917  try {
1918  Xapian::Query qobj = qp.parse_query(p->query);
1919  parsed = qobj.get_description();
1920  expect = string("Query(") + expect + ')';
1921  } catch (const Xapian::QueryParserError &e) {
1922  parsed = e.get_msg();
1923  } catch (const Xapian::Error &e) {
1924  parsed = e.get_description();
1925  } catch (...) {
1926  parsed = "Unknown exception!";
1927  }
1928  tout << "Query: " << p->query << '\n';
1929  TEST_STRINGS_EQUAL(parsed, expect);
1930  }
1931 }
1932 
1933 // Test DateRangeProcessor
1934 DEFINE_TESTCASE(qp_daterange1, !backend) {
1937  qp.add_rangeprocessor(&rp_date);
1938  for (const test *p = test_value_daterange1_queries; p->query; ++p) {
1939  string expect, parsed;
1940  if (p->expect)
1941  expect = p->expect;
1942  else
1943  expect = "parse error";
1944  try {
1945  Xapian::Query qobj = qp.parse_query(p->query);
1946  parsed = qobj.get_description();
1947  expect = string("Query(") + expect + ')';
1948  } catch (const Xapian::QueryParserError &e) {
1949  parsed = e.get_msg();
1950  } catch (const Xapian::Error &e) {
1951  parsed = e.get_description();
1952  } catch (...) {
1953  parsed = "Unknown exception!";
1954  }
1955  tout << "Query: " << p->query << '\n';
1956  TEST_STRINGS_EQUAL(parsed, expect);
1957  }
1958 }
1959 
1960 static const test test_value_daterange2_queries[] = {
1961  { "created:12/03/99..12/04/01", "VALUE_RANGE 1 19991203 20011204" },
1962  { "modified:03-12-99..04-14-01", "VALUE_RANGE 2 19990312 20010414" },
1963  { "accessed:01/30/70..02/02/69", "VALUE_RANGE 3 19700130 20690202" },
1964  // In <=1.2.12, and in 1.3.0, this gave "Unknown range operation":
1965  { "deleted:12/03/99..12/04/01", "VALUE_RANGE 4 19990312 20010412" },
1966  { "1999-03-12..2001-04-14", "Unknown range operation" },
1967  { "12/03/99..created:12/04/01", "Unknown range operation" },
1968  { "12/03/99created:..12/04/01", "Unknown range operation" },
1969  { "12/03/99..12/04/01created:", "Unknown range operation" },
1970  { "12/03/99..02", "Unknown range operation" },
1971  { "1999-03-12..2001", "Unknown range operation" },
1972  { NULL, NULL }
1973 };
1974 
1975 // Feature test DateValueRangeProcessor with prefixes (added in 1.1.2).
1976 DEFINE_TESTCASE(qp_value_daterange2, !backend) {
1978  Xapian::DateValueRangeProcessor vrp_cdate(1, "created:", true, true, 1970);
1979  Xapian::DateValueRangeProcessor vrp_mdate(2, "modified:", true, true, 1970);
1980  Xapian::DateValueRangeProcessor vrp_adate(3, "accessed:", true, true, 1970);
1981  // Regression test - here a const char * was taken as a bool rather than a
1982  // std::string when resolving the overloaded forms. Fixed in 1.2.13 and
1983  // 1.3.1.
1984  Xapian::DateValueRangeProcessor vrp_ddate(4, "deleted:");
1985  qp.add_valuerangeprocessor(&vrp_cdate);
1986  qp.add_valuerangeprocessor(&vrp_mdate);
1987  qp.add_valuerangeprocessor(&vrp_adate);
1988  qp.add_valuerangeprocessor(&vrp_ddate);
1989  for (const test *p = test_value_daterange2_queries; p->query; ++p) {
1990  string expect, parsed;
1991  if (p->expect)
1992  expect = p->expect;
1993  else
1994  expect = "parse error";
1995  try {
1996  Xapian::Query qobj = qp.parse_query(p->query);
1997  parsed = qobj.get_description();
1998  expect = string("Query(") + expect + ')';
1999  } catch (const Xapian::QueryParserError &e) {
2000  parsed = e.get_msg();
2001  } catch (const Xapian::Error &e) {
2002  parsed = e.get_description();
2003  } catch (...) {
2004  parsed = "Unknown exception!";
2005  }
2006  tout << "Query: " << p->query << '\n';
2007  TEST_STRINGS_EQUAL(parsed, expect);
2008  }
2009 }
2010 
2011 // Feature test DateRangeProcessor with prefixes (added in 1.1.2).
2012 DEFINE_TESTCASE(qp_daterange2, !backend) {
2015  Xapian::DateRangeProcessor rp_cdate(1, "created:", RP_DATE_PREFER_MDY, 1970);
2016  Xapian::DateRangeProcessor rp_mdate(2, "modified:", RP_DATE_PREFER_MDY, 1970);
2017  Xapian::DateRangeProcessor rp_adate(3, "accessed:", RP_DATE_PREFER_MDY, 1970);
2018  // Regression test - here a const char * was taken as a bool rather than a
2019  // std::string when resolving the overloaded forms. Fixed in 1.2.13 and
2020  // 1.3.1.
2021  Xapian::DateRangeProcessor rp_ddate(4, "deleted:");
2022  qp.add_rangeprocessor(&rp_cdate);
2023  qp.add_rangeprocessor(&rp_mdate);
2024  qp.add_rangeprocessor(&rp_adate);
2025  qp.add_rangeprocessor(&rp_ddate);
2026  for (const test *p = test_value_daterange2_queries; p->query; ++p) {
2027  string expect, parsed;
2028  if (p->expect)
2029  expect = p->expect;
2030  else
2031  expect = "parse error";
2032  try {
2033  Xapian::Query qobj = qp.parse_query(p->query);
2034  parsed = qobj.get_description();
2035  expect = string("Query(") + expect + ')';
2036  } catch (const Xapian::QueryParserError &e) {
2037  parsed = e.get_msg();
2038  } catch (const Xapian::Error &e) {
2039  parsed = e.get_description();
2040  } catch (...) {
2041  parsed = "Unknown exception!";
2042  }
2043  tout << "Query: " << p->query << '\n';
2044  TEST_STRINGS_EQUAL(parsed, expect);
2045  }
2046 }
2047 
2048 static const test test_value_stringrange1_queries[] = {
2049  { "tag:bar..foo", "VALUE_RANGE 1 bar foo" },
2050  { "bar..foo", "VALUE_RANGE 0 bar foo" },
2051  { NULL, NULL }
2052 };
2053 
2054 // Feature test StringValueRangeProcessor with prefixes (added in 1.1.2).
2055 DEFINE_TESTCASE(qp_value_stringrange1, !backend) {
2057  Xapian::StringValueRangeProcessor vrp_default(0);
2058  Xapian::StringValueRangeProcessor vrp_tag(1, "tag:", true);
2059  qp.add_valuerangeprocessor(&vrp_tag);
2060  qp.add_valuerangeprocessor(&vrp_default);
2061  for (const test *p = test_value_stringrange1_queries; p->query; ++p) {
2062  string expect, parsed;
2063  if (p->expect)
2064  expect = p->expect;
2065  else
2066  expect = "parse error";
2067  try {
2068  Xapian::Query qobj = qp.parse_query(p->query);
2069  parsed = qobj.get_description();
2070  expect = string("Query(") + expect + ')';
2071  } catch (const Xapian::QueryParserError &e) {
2072  parsed = e.get_msg();
2073  } catch (const Xapian::Error &e) {
2074  parsed = e.get_description();
2075  } catch (...) {
2076  parsed = "Unknown exception!";
2077  }
2078  tout << "Query: " << p->query << '\n';
2079  TEST_STRINGS_EQUAL(parsed, expect);
2080  }
2081 }
2082 
2083 // Feature test RangeProcessor with prefixes.
2084 DEFINE_TESTCASE(qp_stringrange1, !backend) {
2086  Xapian::RangeProcessor rp_default(0);
2087  Xapian::RangeProcessor rp_tag(1, "tag:");
2088  qp.add_rangeprocessor(&rp_tag);
2089  qp.add_rangeprocessor(&rp_default);
2090  for (const test *p = test_value_stringrange1_queries; p->query; ++p) {
2091  string expect, parsed;
2092  if (p->expect)
2093  expect = p->expect;
2094  else
2095  expect = "parse error";
2096  try {
2097  Xapian::Query qobj = qp.parse_query(p->query);
2098  parsed = qobj.get_description();
2099  expect = string("Query(") + expect + ')';
2100  } catch (const Xapian::QueryParserError &e) {
2101  parsed = e.get_msg();
2102  } catch (const Xapian::Error &e) {
2103  parsed = e.get_description();
2104  } catch (...) {
2105  parsed = "Unknown exception!";
2106  }
2107  tout << "Query: " << p->query << '\n';
2108  TEST_STRINGS_EQUAL(parsed, expect);
2109  }
2110 }
2111 
2112 static const test test_value_customrange1_queries[] = {
2113  { "mars author:Asimov..Bradbury", "(mars@1 FILTER VALUE_RANGE 4 asimov bradbury)" },
2114  { NULL, NULL }
2115 };
2116 
2119 
2120  Xapian::valueno operator()(std::string &begin, std::string &end) {
2121  if (!startswith(begin, "author:"))
2122  return Xapian::BAD_VALUENO;
2123  begin.erase(0, 7);
2124  begin = Xapian::Unicode::tolower(begin);
2125  end = Xapian::Unicode::tolower(end);
2126  return 4;
2127  }
2128 };
2129 
2130 // Test custom ValueRangeProcessor subclass.
2131 DEFINE_TESTCASE(qp_value_customrange1, !backend) {
2133  AuthorValueRangeProcessor vrp_author;
2134  qp.add_valuerangeprocessor(&vrp_author);
2135  for (const test *p = test_value_customrange1_queries; p->query; ++p) {
2136  string expect, parsed;
2137  if (p->expect)
2138  expect = p->expect;
2139  else
2140  expect = "parse error";
2141  try {
2142  Xapian::Query qobj = qp.parse_query(p->query);
2143  parsed = qobj.get_description();
2144  expect = string("Query(") + expect + ')';
2145  } catch (const Xapian::QueryParserError &e) {
2146  parsed = e.get_msg();
2147  } catch (const Xapian::Error &e) {
2148  parsed = e.get_description();
2149  } catch (...) {
2150  parsed = "Unknown exception!";
2151  }
2152  tout << "Query: " << p->query << '\n';
2153  TEST_STRINGS_EQUAL(parsed, expect);
2154  }
2155 }
2156 
2158  AuthorRangeProcessor() : Xapian::RangeProcessor(4, "author:") { }
2159 
2160  Xapian::Query operator()(const std::string& b,
2161  const std::string& e) override
2162  {
2163  string begin = Xapian::Unicode::tolower(b);
2164  string end = Xapian::Unicode::tolower(e);
2165  return Xapian::RangeProcessor::operator()(begin, end);
2166  }
2167 };
2168 
2169 // Test custom RangeProcessor subclass.
2170 DEFINE_TESTCASE(qp_customrange1, !backend) {
2172  AuthorRangeProcessor rp_author;
2173  qp.add_rangeprocessor(&rp_author);
2174  for (const test *p = test_value_customrange1_queries; p->query; ++p) {
2175  string expect, parsed;
2176  if (p->expect)
2177  expect = p->expect;
2178  else
2179  expect = "parse error";
2180  try {
2181  Xapian::Query qobj = qp.parse_query(p->query);
2182  parsed = qobj.get_description();
2183  expect = string("Query(") + expect + ')';
2184  } catch (const Xapian::QueryParserError &e) {
2185  parsed = e.get_msg();
2186  } catch (const Xapian::Error &e) {
2187  parsed = e.get_description();
2188  } catch (...) {
2189  parsed = "Unknown exception!";
2190  }
2191  tout << "Query: " << p->query << '\n';
2192  TEST_STRINGS_EQUAL(parsed, expect);
2193  }
2194 }
2195 
2197  Xapian::Query operator()(const std::string& str) override {
2198  if (str == "all")
2199  return Xapian::Query::MatchAll;
2200  return Xapian::Query("S" + str);
2201  }
2202 };
2203 
2205  Xapian::Query operator()(const std::string& str) override {
2206  if (str == "*")
2207  return Xapian::Query::MatchAll;
2208  string res = "H";
2209  for (string::const_iterator i = str.begin(); i != str.end(); ++i)
2210  res += C_tolower(*i);
2211  return Xapian::Query(res);
2212  }
2213 };
2214 
2215 static const test test_fieldproc1_queries[] = {
2216  { "title:test", "Stest" },
2217  { "subject:test", "Stest" },
2218  { "title:all", "<alldocuments>" },
2219  { "host:Xapian.org", "0 * Hxapian.org" },
2220  { "host2:Xapian.org", "0 * Hxapian.org" },
2221  { "host:*", "0 * <alldocuments>" },
2222  { "host:\"Space Station.Example.Org\"", "0 * Hspace station.example.org" },
2223  { NULL, NULL }
2224 };
2225 
2226 // FieldProcessor test.
2227 DEFINE_TESTCASE(qp_fieldproc1, !backend) {
2229  TitleFieldProcessor title_fproc;
2230  HostFieldProcessor host_fproc;
2231  qp.add_prefix("title", &title_fproc);
2232  qp.add_prefix("subject:", &title_fproc);
2233  qp.add_boolean_prefix("host", &host_fproc);
2234  qp.add_boolean_prefix("host2:", &host_fproc);
2235  for (const test *p = test_fieldproc1_queries; p->query; ++p) {
2236  string expect, parsed;
2237  if (p->expect)
2238  expect = p->expect;
2239  else
2240  expect = "parse error";
2241  try {
2242  Xapian::Query qobj = qp.parse_query(p->query);
2243  parsed = qobj.get_description();
2244  expect = string("Query(") + expect + ')';
2245  } catch (const Xapian::QueryParserError &e) {
2246  parsed = e.get_msg();
2247  } catch (const Xapian::Error &e) {
2248  parsed = e.get_description();
2249  } catch (...) {
2250  parsed = "Unknown exception!";
2251  }
2252  tout << "Query: " << p->query << '\n';
2253  TEST_STRINGS_EQUAL(parsed, expect);
2254  }
2255 }
2256 
2258  Xapian::Query operator()(const std::string& str) override {
2259  // In reality, these would be built from the current date, but for
2260  // testing it is much simpler to fix the date.
2261  if (str == "today")
2262  return Xapian::Query(Xapian::Query::OP_VALUE_GE, 1, "20120725");
2263  if (str == "this week")
2264  return Xapian::Query(Xapian::Query::OP_VALUE_GE, 1, "20120723");
2265  if (str == "this month")
2266  return Xapian::Query(Xapian::Query::OP_VALUE_GE, 1, "20120701");
2267  if (str == "this year")
2268  return Xapian::Query(Xapian::Query::OP_VALUE_GE, 1, "20120101");
2269  if (str == "this decade")
2270  return Xapian::Query(Xapian::Query::OP_VALUE_GE, 1, "20100101");
2271  if (str == "this century")
2272  return Xapian::Query(Xapian::Query::OP_VALUE_GE, 1, "20000101");
2273  throw Xapian::QueryParserError("Didn't understand date specification '" + str + "'");
2274  }
2275 };
2276 
2277 static const test test_fieldproc2_queries[] = {
2278  { "date:\"this week\"", "VALUE_GE 1 20120723" },
2279  { "date:23/7/2012..25/7/2012", "VALUE_RANGE 1 20120723 20120725" },
2280  { NULL, NULL }
2281 };
2282 
2283 // Test using FieldProcessor and ValueRangeProcessor together.
2284 DEFINE_TESTCASE(qp_fieldproc2, !backend) {
2286  DateRangeFieldProcessor date_fproc;
2287  qp.add_boolean_prefix("date", &date_fproc);
2288  Xapian::DateValueRangeProcessor vrp_date(1, "date:");
2289  qp.add_valuerangeprocessor(&vrp_date);
2290  for (const test *p = test_fieldproc2_queries; p->query; ++p) {
2291  string expect, parsed;
2292  if (p->expect)
2293  expect = p->expect;
2294  else
2295  expect = "parse error";
2296  try {
2297  Xapian::Query qobj = qp.parse_query(p->query);
2298  parsed = qobj.get_description();
2299  expect = string("Query(") + expect + ')';
2300  } catch (const Xapian::QueryParserError &e) {
2301  parsed = e.get_msg();
2302  } catch (const Xapian::Error &e) {
2303  parsed = e.get_description();
2304  } catch (...) {
2305  parsed = "Unknown exception!";
2306  }
2307  tout << "Query: " << p->query << '\n';
2308  TEST_STRINGS_EQUAL(parsed, expect);
2309  }
2310 }
2311 
2312 // Test using FieldProcessor and RangeProcessor together.
2313 DEFINE_TESTCASE(qp_fieldproc3, !backend) {
2315  DateRangeFieldProcessor date_fproc;
2316  qp.add_boolean_prefix("date", &date_fproc);
2317  Xapian::DateRangeProcessor rp_date(1, "date:");
2318  qp.add_rangeprocessor(&rp_date);
2319  for (const test *p = test_fieldproc2_queries; p->query; ++p) {
2320  string expect, parsed;
2321  if (p->expect)
2322  expect = p->expect;
2323  else
2324  expect = "parse error";
2325  try {
2326  Xapian::Query qobj = qp.parse_query(p->query);
2327  parsed = qobj.get_description();
2328  expect = string("Query(") + expect + ')';
2329  } catch (const Xapian::QueryParserError &e) {
2330  parsed = e.get_msg();
2331  } catch (const Xapian::Error &e) {
2332  parsed = e.get_description();
2333  } catch (...) {
2334  parsed = "Unknown exception!";
2335  }
2336  tout << "Query: " << p->query << '\n';
2337  TEST_STRINGS_EQUAL(parsed, expect);
2338  }
2339 }
2340 
2341 DEFINE_TESTCASE(qp_stoplist1, !backend) {
2343  static const char * const stopwords[] = { "a", "an", "the" };
2344  Xapian::SimpleStopper stop(stopwords, stopwords + 3);
2345  qp.set_stopper(&stop);
2346 
2348 
2349  Xapian::Query query1 = qp.parse_query("some mice");
2350  i = qp.stoplist_begin();
2351  TEST(i == qp.stoplist_end());
2352 
2353  Xapian::Query query2 = qp.parse_query("the cat");
2354  i = qp.stoplist_begin();
2355  TEST(i != qp.stoplist_end());
2356  TEST_EQUAL(*i, "the");
2357  ++i;
2358  TEST(i == qp.stoplist_end());
2359 
2360  // Regression test - prior to Xapian 1.0.0 the stoplist wasn't being cleared
2361  // when a new query was parsed.
2362  Xapian::Query query3 = qp.parse_query("an aardvark");
2363  i = qp.stoplist_begin();
2364  TEST(i != qp.stoplist_end());
2365  TEST_EQUAL(*i, "an");
2366  ++i;
2367  TEST(i == qp.stoplist_end());
2368 }
2369 
2370 static const test test_mispelled_queries[] = {
2371  { "doucment search", "document search" },
2372  { "doucment seeacrh", "document search" },
2373  { "docment seeacrh test", "document search test" },
2374  { "\"paragahp pineapple\"", "\"paragraph pineapple\"" },
2375  { "\"paragahp pineapple\"", "\"paragraph pineapple\"" },
2376  { "test S.E.A.R.C.", "" },
2377  { "this AND that", "" },
2378  { "documento", "document" },
2379  { "documento-documento", "document-document" },
2380  { "documento-searcho", "document-search" },
2381  { "test saerch", "test search" },
2382  { "paragraf search", "paragraph search" },
2383  { NULL, NULL }
2384 };
2385 
2386 // Test spelling correction in the QueryParser.
2387 DEFINE_TESTCASE(qp_spell1, spelling) {
2388  Xapian::Database db = get_database("qp_spell1",
2389  [](Xapian::WritableDatabase& wdb,
2390  const string&) {
2391  Xapian::Document doc;
2392  doc.add_term("document", 6);
2393  doc.add_term("search", 7);
2394  doc.add_term("saerch", 1);
2395  doc.add_term("paragraph", 8);
2396  doc.add_term("paragraf", 2);
2397  wdb.add_document(doc);
2398 
2399  wdb.add_spelling("document");
2400  wdb.add_spelling("search");
2401  wdb.add_spelling("paragraph");
2402  wdb.add_spelling("band");
2403  });
2404 
2407  qp.set_database(db);
2408 
2409  for (const test *p = test_mispelled_queries; p->query; ++p) {
2410  Xapian::Query q;
2411  q = qp.parse_query(p->query,
2414  tout << "Query: " << p->query << '\n';
2415  TEST_STRINGS_EQUAL(qp.get_corrected_query_string(), p->expect);
2416  }
2417 }
2418 
2419 // Test spelling correction in the QueryParser with multiple databases.
2420 DEFINE_TESTCASE(qp_spell2, spelling)
2421 {
2422  Xapian::Database db1 = get_database("qp_spell2a",
2423  [](Xapian::WritableDatabase& wdb,
2424  const string&) {
2425  wdb.add_spelling("document");
2426  wdb.add_spelling("search");
2427  });
2428  Xapian::Database db2 = get_database("qp_spell2b",
2429  [](Xapian::WritableDatabase& wdb,
2430  const string&) {
2431  wdb.add_spelling("document");
2432  wdb.add_spelling("paragraph");
2433  });
2434  Xapian::Database db;
2435  db.add_database(db1);
2436  db.add_database(db2);
2437 
2440  qp.set_database(db);
2441 
2442  for (const test *p = test_mispelled_queries; p->query; ++p) {
2443  Xapian::Query q;
2444  q = qp.parse_query(p->query,
2447  tout << "Query: " << p->query << '\n';
2449  }
2450 }
2451 
2452 static void
2454 {
2455  db.add_spelling("document");
2456  db.add_spelling("search");
2457  db.add_spelling("paragraph");
2458  db.add_spelling("band");
2459 }
2460 
2461 static const test test_mispelled_wildcard_queries[] = {
2462  { "doucment", "document" },
2463  { "doucment*", "" },
2464  { "doucment* seearch", "doucment* search" },
2465  { "doucment* search", "" },
2466  { NULL, NULL }
2467 };
2468 
2469 // Test spelling correction in the QueryParser with wildcards.
2470 // Regression test for bug fixed in 1.1.3 and 1.0.17.
2471 DEFINE_TESTCASE(qp_spellwild1, spelling) {
2472  Xapian::Database db = get_database("simple_spelling_db",
2475  qp.set_database(db);
2476 
2477  const test *p;
2478  for (p = test_mispelled_queries; p->query; ++p) {
2479  Xapian::Query q;
2480  q = qp.parse_query(p->query,
2484  tout << "Query: " << p->query << '\n';
2486  }
2487  for (p = test_mispelled_wildcard_queries; p->query; ++p) {
2488  Xapian::Query q;
2489  q = qp.parse_query(p->query,
2493  tout << "Query: " << p->query << '\n';
2495  }
2496 }
2497 
2498 static const test test_mispelled_partial_queries[] = {
2499  { "doucment", "" },
2500  { "doucment ", "document " },
2501  { "documen", "" },
2502  { "documen ", "document " },
2503  { "seearch documen", "search documen" },
2504  { "search documen", "" },
2505  { NULL, NULL }
2506 };
2507 
2508 // Test spelling correction in the QueryParser with FLAG_PARTIAL.
2509 // Regression test for bug fixed in 1.1.3 and 1.0.17.
2510 DEFINE_TESTCASE(qp_spellpartial1, spelling) {
2511  Xapian::Database db = get_database("simple_spelling_db",
2514  qp.set_database(db);
2515 
2516  for (const test *p = test_mispelled_partial_queries; p->query; ++p) {
2517  Xapian::Query q;
2518  q = qp.parse_query(p->query,
2521  tout << "Query: " << p->query << '\n';
2523  }
2524 }
2525 
2526 static const test test_synonym_queries[] = {
2527  { "searching", "(Zsearch@1 SYNONYM Zfind@1 SYNONYM Zlocate@1)" },
2528  { "search", "(Zsearch@1 SYNONYM find@1)" },
2529  { "Search", "(search@1 SYNONYM find@1)" },
2530  { "Searching", "searching@1" },
2531  { "searching OR terms", "((Zsearch@1 SYNONYM Zfind@1 SYNONYM Zlocate@1) OR Zterm@2)" },
2532  { "search OR terms", "((Zsearch@1 SYNONYM find@1) OR Zterm@2)" },
2533  { "search +terms", "(Zterm@2 AND_MAYBE (Zsearch@1 SYNONYM find@1))" },
2534  { "search -terms", "((Zsearch@1 SYNONYM find@1) AND_NOT Zterm@2)" },
2535  { "+search terms", "((Zsearch@1 SYNONYM find@1) AND_MAYBE Zterm@2)" },
2536  { "-search terms", "(Zterm@2 AND_NOT (Zsearch@1 SYNONYM find@1))" },
2537  { "search terms", "((Zsearch@1 SYNONYM find@1) OR Zterm@2)" },
2538  // Shouldn't trigger synonyms:
2539  { "\"search terms\"", "(search@1 PHRASE 2 terms@2)" },
2540  // Check that setting FLAG_AUTO_SYNONYMS doesn't enable multi-word
2541  // synonyms. Regression test for bug fixed in 1.3.0 and 1.2.9.
2542  { "regression test", "(Zregress@1 OR Ztest@2)" },
2543  { NULL, NULL }
2544 };
2545 
2546 // Test single term synonyms in the QueryParser.
2547 DEFINE_TESTCASE(qp_synonym1, synonyms) {
2548  Xapian::Database db = get_database("qp_synonym1",
2549  [](Xapian::WritableDatabase& wdb,
2550  const string&) {
2551  wdb.add_synonym("Zsearch", "Zfind");
2552  wdb.add_synonym("Zsearch",
2553  "Zlocate");
2554  wdb.add_synonym("search", "find");
2555  wdb.add_synonym("Zseek", "Zsearch");
2556  wdb.add_synonym("regression test",
2557  "magic");
2558  });
2559 
2561  qp.set_stemmer(Xapian::Stem("english"));
2562  qp.set_stemming_strategy(Xapian::QueryParser::STEM_SOME);
2563  qp.set_database(db);
2564 
2565  for (const test *p = test_synonym_queries; p->query; ++p) {
2566  string expect = "Query(";
2567  expect += p->expect;
2568  expect += ')';
2569  Xapian::Query q;
2570  q = qp.parse_query(p->query, qp.FLAG_AUTO_SYNONYMS|qp.FLAG_DEFAULT);
2571  tout << "Query: " << p->query << '\n';
2572  TEST_STRINGS_EQUAL(q.get_description(), expect);
2573  }
2574 }
2575 
2576 static const test test_multi_synonym_queries[] = {
2577  { "sun OR tan OR cream", "(Zsun@1 OR Ztan@2 OR Zcream@3)" },
2578  { "sun tan", "((Zsun@1 OR Ztan@2) SYNONYM bathe@1)" },
2579  { "sun tan cream", "((Zsun@1 OR Ztan@2 OR Zcream@3) SYNONYM lotion@1)" },
2580  { "beach sun tan holiday", "(Zbeach@1 OR ((Zsun@2 OR Ztan@3) SYNONYM bathe@2) OR Zholiday@4)" },
2581  { "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))" },
2582  { "single", "(Zsingl@1 SYNONYM record@1)" },
2583  { NULL, NULL }
2584 };
2585 
2586 // Test multi term synonyms in the QueryParser.
2587 DEFINE_TESTCASE(qp_synonym2, synonyms) {
2588  Xapian::Database db = get_database("qp_synonym2",
2589  [](Xapian::WritableDatabase& wdb,
2590  const string&) {
2591  wdb.add_synonym("sun tan cream",
2592  "lotion");
2593  wdb.add_synonym("sun tan", "bathe");
2594  wdb.add_synonym("single", "record");
2595  });
2596 
2598  qp.set_stemmer(Xapian::Stem("english"));
2599  qp.set_stemming_strategy(Xapian::QueryParser::STEM_SOME);
2600  qp.set_database(db);
2601 
2602  for (const test *p = test_multi_synonym_queries; p->query; ++p) {
2603  string expect = "Query(";
2604  expect += p->expect;
2605  expect += ')';
2606  Xapian::Query q;
2607  q = qp.parse_query(p->query,
2610  tout << "Query: " << p->query << '\n';
2611  TEST_STRINGS_EQUAL(q.get_description(), expect);
2612  }
2613 }
2614 
2615 static const test test_synonym_op_queries[] = {
2616  { "searching", "Zsearch@1" },
2617  { "~searching", "(Zsearch@1 SYNONYM Zfind@1 SYNONYM Zlocate@1)" },
2618  { "~search", "(Zsearch@1 SYNONYM find@1)" },
2619  { "~Search", "(search@1 SYNONYM find@1)" },
2620  { "~Searching", "searching@1" },
2621  { "~searching OR terms", "((Zsearch@1 SYNONYM Zfind@1 SYNONYM Zlocate@1) OR Zterm@2)" },
2622  { "~search OR terms", "((Zsearch@1 SYNONYM find@1) OR Zterm@2)" },
2623  { "~search +terms", "(Zterm@2 AND_MAYBE (Zsearch@1 SYNONYM find@1))" },
2624  { "~search -terms", "((Zsearch@1 SYNONYM find@1) AND_NOT Zterm@2)" },
2625  { "+~search terms", "((Zsearch@1 SYNONYM find@1) AND_MAYBE Zterm@2)" },
2626  { "-~search terms", "(Zterm@2 AND_NOT (Zsearch@1 SYNONYM find@1))" },
2627  { "~search terms", "((Zsearch@1 SYNONYM find@1) OR Zterm@2)" },
2628  { "~foo:search", "(ZXFOOsearch@1 SYNONYM prefixated@1)" },
2629  // FIXME: should look for multi-term synonym...
2630  { "~\"search terms\"", "(search@1 PHRASE 2 terms@2)" },
2631  { NULL, NULL }
2632 };
2633 
2634 // Test the synonym operator in the QueryParser.
2635 DEFINE_TESTCASE(qp_synonym3, synonyms) {
2636  Xapian::Database db = get_database("qp_synonym3",
2637  [](Xapian::WritableDatabase& wdb,
2638  const string&) {
2639  wdb.add_synonym("Zsearch", "Zfind");
2640  wdb.add_synonym("Zsearch",
2641  "Zlocate");
2642  wdb.add_synonym("search", "find");
2643  wdb.add_synonym("Zseek", "Zsearch");
2644  wdb.add_synonym("ZXFOOsearch",
2645  "prefixated");
2646  });
2647 
2649  qp.set_stemmer(Xapian::Stem("english"));
2650  qp.set_stemming_strategy(Xapian::QueryParser::STEM_SOME);
2651  qp.set_database(db);
2652  qp.add_prefix("foo", "XFOO");
2653 
2654  for (const test *p = test_synonym_op_queries; p->query; ++p) {
2655  string expect = "Query(";
2656  expect += p->expect;
2657  expect += ')';
2658  Xapian::Query q;
2659  q = qp.parse_query(p->query,
2664  tout << "Query: " << p->query << '\n';
2665  TEST_STRINGS_EQUAL(q.get_description(), expect);
2666  }
2667 }
2668 
2669 static const test test_stem_all_queries[] = {
2670  { "\"chemical engineers\"", "(chemic@1 PHRASE 2 engin@2)" },
2671  { "chemical NEAR engineers", "(chemic@1 NEAR 11 engin@2)" },
2672  { "chemical engineers", "(chemic@1 OR engin@2)" },
2673  { "title:(chemical engineers)", "(XTchemic@1 OR XTengin@2)" },
2674  { NULL, NULL }
2675 };
2676 
2677 DEFINE_TESTCASE(qp_stem_all1, !backend) {
2679  qp.set_stemmer(Xapian::Stem("english"));
2681  qp.add_prefix("title", "XT");
2682  for (const test *p = test_stem_all_queries; p->query; ++p) {
2683  string expect, parsed;
2684  if (p->expect)
2685  expect = p->expect;
2686  else
2687  expect = "parse error";
2688  try {
2689  Xapian::Query qobj = qp.parse_query(p->query);
2690  parsed = qobj.get_description();
2691  expect = string("Query(") + expect + ')';
2692  } catch (const Xapian::QueryParserError &e) {
2693  parsed = e.get_msg();
2694  } catch (const Xapian::Error &e) {
2695  parsed = e.get_description();
2696  } catch (...) {
2697  parsed = "Unknown exception!";
2698  }
2699  tout << "Query: " << p->query << '\n';
2700  TEST_STRINGS_EQUAL(parsed, expect);
2701  }
2702 }
2703 
2704 static const test test_stem_all_z_queries[] = {
2705  { "\"chemical engineers\"", "(Zchemic@1 PHRASE 2 Zengin@2)" },
2706  { "chemical NEAR engineers", "(Zchemic@1 NEAR 11 Zengin@2)" },
2707  { "chemical engineers", "(Zchemic@1 OR Zengin@2)" },
2708  { "title:(chemical engineers)", "(ZXTchemic@1 OR ZXTengin@2)" },
2709  { NULL, NULL }
2710 };
2711 
2712 DEFINE_TESTCASE(qp_stem_all_z1, !backend) {
2714  qp.set_stemmer(Xapian::Stem("english"));
2716  qp.add_prefix("title", "XT");
2717  for (const test *p = test_stem_all_z_queries; p->query; ++p) {
2718  string expect, parsed;
2719  if (p->expect)
2720  expect = p->expect;
2721  else
2722  expect = "parse error";
2723  try {
2724  Xapian::Query qobj = qp.parse_query(p->query);
2725  parsed = qobj.get_description();
2726  expect = string("Query(") + expect + ')';
2727  } catch (const Xapian::QueryParserError &e) {
2728  parsed = e.get_msg();
2729  } catch (const Xapian::Error &e) {
2730  parsed = e.get_description();
2731  } catch (...) {
2732  parsed = "Unknown exception!";
2733  }
2734  tout << "Query: " << p->query << '\n';
2735  TEST_STRINGS_EQUAL(parsed, expect);
2736  }
2737 }
2738 
2739 static double
2740 time_query_parse(const Xapian::Database & db, const string & q,
2741  int repetitions, unsigned flags)
2742 {
2744  qp.set_database(db);
2745  CPUTimer timer;
2746  std::vector<Xapian::Query> qs;
2747  qs.reserve(repetitions);
2748  for (int i = 0; i != repetitions; ++i) {
2749  qs.push_back(qp.parse_query(q, flags));
2750  }
2751  if (repetitions > 1) {
2752  Xapian::Query qc(Xapian::Query::OP_OR, qs.begin(), qs.end());
2753  }
2754  return timer.get_time();
2755 }
2756 
2757 static void
2758 qp_scale1_helper(const Xapian::Database &db, const string & q, unsigned n,
2759  unsigned flags)
2760 {
2761  double time1;
2762  while (true) {
2763  time1 = time_query_parse(db, q, n, flags);
2764  if (time1 != 0.0) break;
2765 
2766  // The first test completed before the timer ticked at all, so increase
2767  // the number of repetitions and retry.
2768  unsigned n_new = n * 10;
2769  if (n_new < n)
2770  SKIP_TEST("Can't count enough repetitions to be able to time test");
2771  n = n_new;
2772  }
2773 
2774  n /= 5;
2775 
2776  string q_n;
2777  q_n.reserve(q.size() * n);
2778  for (unsigned i = n; i != 0; --i) {
2779  q_n += q;
2780  }
2781 
2782  // Time 5 repetitions so we average random variations a bit.
2783  double time2 = time_query_parse(db, q_n, 5, flags);
2784  tout << "small=" << time1 << "s, large=" << time2 << "s\n";
2785 
2786  // Allow a factor of 2.15 difference, to cover random variation and a
2787  // native time interval which isn't an exact multiple of 1/CLK_TCK.
2788  TEST_REL(time2,<,time1 * 2.15);
2789 }
2790 
2791 // Regression test: check that query parser doesn't scale very badly with the
2792 // size of the query.
2793 DEFINE_TESTCASE(qp_scale1, writable && synonyms) {
2795 
2796  db.add_synonym("foo", "bar");
2797  db.commit();
2798 
2799  string q1("foo ");
2800  string q1b("baz ");
2801  const unsigned repetitions = 5000;
2802 
2803  // A long multiword synonym.
2804  string syn;
2805  for (int j = 50; j != 0; --j) {
2806  syn += q1;
2807  }
2808  syn.resize(syn.size() - 1);
2809 
2810  unsigned synflags = Xapian::QueryParser::FLAG_DEFAULT |
2813 
2814  // First, we test a simple query.
2816 
2817  // If synonyms are enabled, a different code-path is followed.
2818  // Test a query which has no synonyms.
2819  qp_scale1_helper(db, q1b, repetitions, synflags);
2820 
2821  // Test a query which has short synonyms.
2822  qp_scale1_helper(db, q1, repetitions, synflags);
2823 
2824  // Add a synonym for the whole query, to test that code path.
2825  db.add_synonym(syn, "bar");
2826  db.commit();
2827 
2828  qp_scale1_helper(db, q1, repetitions, synflags);
2829 }
2830 
2831 static const test test_near_queries[] = {
2832  { "simple-example", "(simple@1 PHRASE 2 example@2)" },
2833  { "stock -cooking", "(Zstock@1 AND_NOT Zcook@2)" },
2834 // FIXME: these give NEAR 2
2835 // { "foo -baz bar", "((foo@1 NEAR 11 bar@3) AND_NOT Zbaz@2)" },
2836 // { "one +two three", "(Ztwo@2 AND_MAYBE (one@1 NEAR 11 three@3))" },
2837  { "foo bar", "(foo@1 NEAR 11 bar@2)" },
2838  { "foo bar baz", "(foo@1 NEAR 12 bar@2 NEAR 12 baz@3)" },
2839  { "gtk+ -gnome", "(Zgtk+@1 AND_NOT Zgnome@2)" },
2840  { "c++ -d--", "(Zc++@1 AND_NOT Zd@2)" },
2841  { "\"c++ library\"", "(c++@1 PHRASE 2 library@2)" },
2842  { "author:orwell animal farm", "(Aorwell@1 NEAR 12 animal@2 NEAR 12 farm@3)" },
2843  { "author:Orwell Animal Farm", "(Aorwell@1 NEAR 12 animal@2 NEAR 12 farm@3)" },
2844  { "beer NOT \"orange juice\"", "(Zbeer@1 AND_NOT (orange@2 PHRASE 2 juice@3))" },
2845  { "beer AND NOT lager", "(Zbeer@1 AND_NOT Zlager@2)" },
2846  { "A OR B NOT C", "(a@1 OR (b@2 AND_NOT c@3))" },
2847  { "A OR B AND NOT C", "(a@1 OR (b@2 AND_NOT c@3))" },
2848  { "A OR B XOR C", "(a@1 OR (b@2 XOR c@3))" },
2849  { "A XOR B NOT C", "(a@1 XOR (b@2 AND_NOT c@3))" },
2850  { "one AND two", "(Zone@1 AND Ztwo@2)" },
2851  { "NOT windows", "Syntax: <expression> NOT <expression>" },
2852  { "a AND (NOT b)", "Syntax: <expression> NOT <expression>" },
2853  { "AND NOT windows", "Syntax: <expression> AND NOT <expression>" },
2854  { "gordian NOT", "Syntax: <expression> NOT <expression>" },
2855  { "gordian AND NOT", "Syntax: <expression> AND NOT <expression>" },
2856  { "foo OR (something AND)", "Syntax: <expression> AND <expression>" },
2857  { "OR foo", "Syntax: <expression> OR <expression>" },
2858  { "XOR", "Syntax: <expression> XOR <expression>" },
2859  { "hard\xa0space", "(hard@1 NEAR 11 space@2)" },
2860  { NULL, NULL }
2861 };
2862 
2863 DEFINE_TESTCASE(qp_near1, !backend) {
2864  Xapian::QueryParser queryparser;
2865  queryparser.set_stemmer(Xapian::Stem("english"));
2867  queryparser.add_prefix("author", "A");
2868  queryparser.add_prefix("writer", "A");
2869  queryparser.add_prefix("title", "XT");
2870  queryparser.add_prefix("subject", "XT");
2871  queryparser.add_prefix("authortitle", "A");
2872  queryparser.add_prefix("authortitle", "XT");
2873  queryparser.add_boolean_prefix("site", "H");
2874  queryparser.add_boolean_prefix("site2", "J");
2875  queryparser.add_boolean_prefix("multisite", "H");
2876  queryparser.add_boolean_prefix("multisite", "J");
2877  queryparser.add_boolean_prefix("category", "XCAT", false);
2878  queryparser.add_boolean_prefix("dogegory", "XDOG", false);
2880  for (const test *p = test_near_queries; p->query; ++p) {
2881  string expect, parsed;
2882  if (p->expect)
2883  expect = p->expect;
2884  else
2885  expect = "parse error";
2886  try {
2887  Xapian::Query qobj = queryparser.parse_query(p->query);
2888  parsed = qobj.get_description();
2889  expect = string("Query(") + expect + ')';
2890  } catch (const Xapian::QueryParserError &e) {
2891  parsed = e.get_msg();
2892  } catch (const Xapian::Error &e) {
2893  parsed = e.get_description();
2894  } catch (...) {
2895  parsed = "Unknown exception!";
2896  }
2897  tout << "Query: " << p->query << '\n';
2898  TEST_STRINGS_EQUAL(parsed, expect);
2899  }
2900 }
2901 
2902 static const test test_phrase_queries[] = {
2903  { "simple-example", "(simple@1 PHRASE 2 example@2)" },
2904  { "stock -cooking", "(Zstock@1 AND_NOT Zcook@2)" },
2905 // FIXME: these give PHRASE 2
2906 // { "foo -baz bar", "((foo@1 PHRASE 11 bar@3) AND_NOT Zbaz@2)" },
2907 // { "one +two three", "(Ztwo@2 AND_MAYBE (one@1 PHRASE 11 three@3))" },
2908  { "foo bar", "(foo@1 PHRASE 11 bar@2)" },
2909  { "foo bar baz", "(foo@1 PHRASE 12 bar@2 PHRASE 12 baz@3)" },
2910  { "gtk+ -gnome", "(Zgtk+@1 AND_NOT Zgnome@2)" },
2911  { "c++ -d--", "(Zc++@1 AND_NOT Zd@2)" },
2912  { "\"c++ library\"", "(c++@1 PHRASE 2 library@2)" },
2913  { "author:orwell animal farm", "(Aorwell@1 PHRASE 12 animal@2 PHRASE 12 farm@3)" },
2914  { "author:Orwell Animal Farm", "(Aorwell@1 PHRASE 12 animal@2 PHRASE 12 farm@3)" },
2915  { "beer NOT \"orange juice\"", "(Zbeer@1 AND_NOT (orange@2 PHRASE 2 juice@3))" },
2916  { "beer AND NOT lager", "(Zbeer@1 AND_NOT Zlager@2)" },
2917  { "A OR B NOT C", "(a@1 OR (b@2 AND_NOT c@3))" },
2918  { "A OR B AND NOT C", "(a@1 OR (b@2 AND_NOT c@3))" },
2919  { "A OR B XOR C", "(a@1 OR (b@2 XOR c@3))" },
2920  { "A XOR B NOT C", "(a@1 XOR (b@2 AND_NOT c@3))" },
2921  { "one AND two", "(Zone@1 AND Ztwo@2)" },
2922  { "NOT windows", "Syntax: <expression> NOT <expression>" },
2923  { "a AND (NOT b)", "Syntax: <expression> NOT <expression>" },
2924  { "AND NOT windows", "Syntax: <expression> AND NOT <expression>" },
2925  { "gordian NOT", "Syntax: <expression> NOT <expression>" },
2926  { "gordian AND NOT", "Syntax: <expression> AND NOT <expression>" },
2927  { "foo OR (something AND)", "Syntax: <expression> AND <expression>" },
2928  { "OR foo", "Syntax: <expression> OR <expression>" },
2929  { "XOR", "Syntax: <expression> XOR <expression>" },
2930  { "hard\xa0space", "(hard@1 PHRASE 11 space@2)" },
2931  // FIXME: this isn't what we want, but fixing phrase to work with
2932  // subqueries first might be the best approach.
2933  // FIXME: this isn't currently reimplemented:
2934  // { "(one AND two) three", "((Zone@1 PHRASE 11 Zthree@3) AND (Ztwo@2 PHRASE 11 Zthree@3))" },
2935  { NULL, NULL }
2936 };
2937 
2938 DEFINE_TESTCASE(qp_phrase1, !backend) {
2939  Xapian::QueryParser queryparser;
2940  queryparser.set_stemmer(Xapian::Stem("english"));
2942  queryparser.add_prefix("author", "A");
2943  queryparser.add_prefix("writer", "A");
2944  queryparser.add_prefix("title", "XT");
2945  queryparser.add_prefix("subject", "XT");
2946  queryparser.add_prefix("authortitle", "A");
2947  queryparser.add_prefix("authortitle", "XT");
2948  queryparser.add_boolean_prefix("site", "H");
2949  queryparser.add_boolean_prefix("site2", "J");
2950  queryparser.add_boolean_prefix("multisite", "H");
2951  queryparser.add_boolean_prefix("multisite", "J");
2952  queryparser.add_boolean_prefix("category", "XCAT", false);
2953  queryparser.add_boolean_prefix("dogegory", "XDOG", false);
2955  for (const test *p = test_phrase_queries; p->query; ++p) {
2956  string expect, parsed;
2957  if (p->expect)
2958  expect = p->expect;
2959  else
2960  expect = "parse error";
2961  try {
2962  Xapian::Query qobj = queryparser.parse_query(p->query);
2963  parsed = qobj.get_description();
2964  expect = string("Query(") + expect + ')';
2965  } catch (const Xapian::QueryParserError &e) {
2966  parsed = e.get_msg();
2967  } catch (const Xapian::Error &e) {
2968  parsed = e.get_description();
2969  } catch (...) {
2970  parsed = "Unknown exception!";
2971  }
2972  tout << "Query: " << p->query << '\n';
2973  TEST_STRINGS_EQUAL(parsed, expect);
2974  }
2975 }
2976 
2977 static const test test_stopword_group_or_queries[] = {
2978  { "this is a test", "test@4" },
2979  { "test*", "WILDCARD SYNONYM test" },
2980  { "a test*", "WILDCARD SYNONYM test" },
2981  { "is a test*", "WILDCARD SYNONYM test" },
2982  { "this is a test*", "WILDCARD SYNONYM test" },
2983  { "this is a us* test*", "(WILDCARD SYNONYM us OR WILDCARD SYNONYM test)" },
2984  { "this is a user test*", "(user@4 OR WILDCARD SYNONYM test)" },
2985  { NULL, NULL }
2986 };
2987 
2988 static const test test_stopword_group_and_queries[] = {
2989  { "this is a test", "test@4" },
2990  { "test*", "WILDCARD SYNONYM test" },
2991  { "a test*", "WILDCARD SYNONYM test" },
2992  // Two stopwords + one wildcard failed in 1.0.16
2993  { "is a test*", "WILDCARD SYNONYM test" },
2994  // Three stopwords + one wildcard failed in 1.0.16
2995  { "this is a test*", "WILDCARD SYNONYM test" },
2996  // Three stopwords + two wildcards failed in 1.0.16
2997  { "this is a us* test*", "(WILDCARD SYNONYM us AND WILDCARD SYNONYM test)" },
2998  { "this is a user test*", "(user@4 AND WILDCARD SYNONYM test)" },
2999  { NULL, NULL }
3000 };
3001 
3002 // Regression test for bug fixed in 1.0.17 and 1.1.3.
3003 DEFINE_TESTCASE(qp_stopword_group1, backend) {
3004  Xapian::Database db = get_database("qp_stopword_group1",
3005  [](Xapian::WritableDatabase& wdb,
3006  const string&) {
3007  Xapian::Document doc;
3008  doc.add_term("test");
3009  doc.add_term("tester");
3010  doc.add_term("testable");
3011  doc.add_term("user");
3012  wdb.add_document(doc);
3013  });
3014 
3015  Xapian::SimpleStopper stopper;
3016  stopper.add("this");
3017  stopper.add("is");
3018  stopper.add("a");
3019 
3021  qp.set_stopper(&stopper);
3022  qp.set_database(db);
3023 
3024  // Process test cases with OP_OR first, then with OP_AND.
3026  const test *p = test_stopword_group_or_queries;
3027  for (int i = 1; i <= 2; ++i) {
3028  for ( ; p->query; ++p) {
3029  string expect, parsed;
3030  if (p->expect)
3031  expect = p->expect;
3032  else
3033  expect = "parse error";
3034  try {
3035  Xapian::Query qobj = qp.parse_query(p->query, qp.FLAG_WILDCARD);
3036  parsed = qobj.get_description();
3037  expect = string("Query(") + expect + ')';
3038  } catch (const Xapian::QueryParserError &e) {
3039  parsed = e.get_msg();
3040  } catch (const Xapian::Error &e) {
3041  parsed = e.get_description();
3042  } catch (...) {
3043  parsed = "Unknown exception!";
3044  }
3045  tout << "Query: " << p->query << '\n';
3046  TEST_STRINGS_EQUAL(parsed, expect);
3047  }
3048 
3051  }
3052 }
3053 
3055 DEFINE_TESTCASE(qp_default_op2, !backend) {
3057  static const Xapian::Query::op ops[] = {
3066  };
3067  for (Xapian::Query::op op : ops) {
3068  tout << op << '\n';
3070  qp.set_default_op(op));
3072  }
3073 }
3074 
3077  const char *expect;
3078 };
3079 
3081 DEFINE_TESTCASE(qp_default_op3, !backend) {
3083  static const qp_default_op3_test tests[] = {
3085  "Query((a@1 AND b@2 AND c@3))" },
3087  "Query((a@1 OR b@2 OR c@3))" },
3089  "Query((a@1 PHRASE 12 b@2 PHRASE 12 c@3))" },
3091  "Query((a@1 NEAR 12 b@2 NEAR 12 c@3))" },
3093  "Query((a@1 ELITE_SET 10 b@2 ELITE_SET 10 c@3))" },
3095  "Query((a@1 SYNONYM b@2 SYNONYM c@3))" },
3096  };
3097  const qp_default_op3_test * p;
3098  for (p = tests; p - tests != sizeof(tests) / sizeof(*tests); ++p) {
3099  tout << p->op << '\n';
3100  qp.set_default_op(p->op);
3101  // Check that get_default_op() returns what we just set.
3102  TEST_EQUAL(qp.get_default_op(), p->op);
3103  TEST_EQUAL(qp.parse_query("A B C").get_description(), p->expect);
3104  }
3105 }
3106 
3108 DEFINE_TESTCASE(qp_defaultstrategysome1, !backend) {
3110  qp.set_stemmer(Xapian::Stem("en"));
3111  TEST_EQUAL(qp.parse_query("testing").get_description(), "Query(Ztest@1)");
3112 }
3113 
3115 DEFINE_TESTCASE(qp_stemsomefullpos, !backend) {
3117  qp.set_stemmer(Xapian::Stem("en"));
3119  TEST_EQUAL(qp.parse_query("terms NEAR testing").get_description(), "Query((Zterm@1 NEAR 11 Ztest@2))");
3120  TEST_EQUAL(qp.parse_query("terms ADJ testing").get_description(), "Query((Zterm@1 PHRASE 11 Ztest@2))");
3121 }
3122 
3123 DEFINE_TESTCASE(qp_nopos, !backend) {
3124  static const test tests[] = {
3125  { "no pos anyway", "(no@1 OR pos@2 OR anyway@3)" },
3126  { "w ADJ x", "(w@1 AND x@2)" },
3127  { "\"phrase q\" OR A NEAR/4 B", "((phrase@1 AND q@2) OR (a@3 AND b@4))" },
3128  // Check FLAG_NO_POSITIONS stays on if we reparse with fewer flags.
3129  { "a-b NEAR x", "((a@1 AND b@2) OR (near@3 OR x@4))" },
3130  };
3132  const auto flags = qp.FLAG_DEFAULT | qp.FLAG_NO_POSITIONS;
3133  for (const test& p : tests) {
3134  string expect, parsed;
3135  if (p.expect)
3136  expect = p.expect;
3137  else
3138  expect = "parse error";
3139  try {
3140  Xapian::Query q = qp.parse_query(p.query, flags);
3141  parsed = q.get_description();
3142  expect = string("Query(") + expect + ')';
3143  } catch (const Xapian::QueryParserError& e) {
3144  parsed = e.get_msg();
3145  } catch (const Xapian::Error& e) {
3146  parsed = e.get_description();
3147  } catch (...) {
3148  parsed = "Unknown exception!";
3149  }
3150  tout << "Query: " << p.query << '\n';
3151  TEST_STRINGS_EQUAL(parsed, expect);
3152  }
3153 }
static Xapian::Query query(Xapian::Query::op op, const string &t1=string(), const string &t2=string(), const string &t3=string(), const string &t4=string(), const string &t5=string(), const string &t6=string(), const string &t7=string(), const string &t8=string(), const string &t9=string(), const string &t10=string())
Definition: api_anydb.cc:63
static const test test_mispelled_queries[]
static const test test_stem_all_queries[]
DEFINE_TESTCASE(queryparser1, !backend)
static const test test_pure_not_queries[]
static void gen_simple_spelling_db(Xapian::WritableDatabase &db, const string &)
static const test test_and_queries[]
static void test_qp_flag_wildcard3_helper(const Xapian::Database &db, Xapian::termcount max_expansion, const string &query_string)
static const test test_value_daterange2_queries[]
static const test test_multi_synonym_queries[]
static const test test_value_range4_queries[]
static const test test_value_stringrange1_queries[]
static const test test_value_customrange1_queries[]
static const test test_stopword_group_or_queries[]
static const test test_stop_queries[]
static const test test_stopword_group_and_queries[]
static const test test_value_range1_queries[]
static const test test_fieldproc2_queries[]
static const test test_stem_all_z_queries[]
static const test test_or_queries[]
static double time_query_parse(const Xapian::Database &db, const string &q, int repetitions, unsigned flags)
static const test test_value_range2_queries[]
static const test test_synonym_op_queries[]
static const test test_phrase_queries[]
static void gen_qp_range3_db(Xapian::WritableDatabase &db, const string &)
static void qp_scale1_helper(const Xapian::Database &db, const string &q, unsigned n, unsigned flags)
static const test test_mispelled_wildcard_queries[]
static const test test_value_daterange1_queries[]
static const test test_fieldproc1_queries[]
static const test test_mispelled_partial_queries[]
static const test test_synonym_queries[]
static const test test_near_queries[]
static void gen_qp_flag_partial1_db(Xapian::WritableDatabase &db, const string &)
Xapian::WritableDatabase get_writable_database(const string &dbname)
Definition: apitest.cc:87
Xapian::Database get_database(const string &dbname)
Definition: apitest.cc:48
test functionality of the Xapian API
double get_time() const
Return elapsed CPU time since object creation in seconds.
Definition: cputimer.h:34
Xapian::Query operator()(const std::string &str) override
Convert a field-prefixed string to a Query object.
Xapian::Query operator()(const std::string &str) override
Convert a field-prefixed string to a Query object.
Xapian::Query operator()(const std::string &str) override
Convert a field-prefixed string to a Query object.
This class is used to access a database, or a group of databases.
Definition: database.h:68
void add_database(const Database &database)
Add an existing database (or group of databases) to those accessed by this object.
Definition: omdatabase.cc:148
Handle a date range.
Definition: queryparser.h:255
Handle a date range.
Definition: queryparser.h:539
A handle representing a document in a Xapian database.
Definition: document.h:61
void add_value(Xapian::valueno slot, const std::string &value)
Add a new value.
Definition: omdocument.cc:107
void add_term(const std::string &tname, Xapian::termcount wdfinc=1)
Add a term to the document, without positional information.
Definition: omdocument.cc:140
This class provides an interface to the information retrieval system for the purpose of searching.
Definition: enquire.h:152
void set_query(const Xapian::Query &query, Xapian::termcount qlen=0)
Set the query to run.
Definition: omenquire.cc:793
MSet get_mset(Xapian::doccount first, Xapian::doccount maxitems, Xapian::doccount checkatleast=0, const RSet *omrset=0, const MatchDecider *mdecider=0) const
Get (a portion of) the match set for the current query.
Definition: omenquire.cc:938
All exceptions thrown by Xapian are subclasses of Xapian::Error.
Definition: error.h:43
const std::string & get_msg() const
Message giving details of the error, intended for human consumption.
Definition: error.h:122
std::string get_description() const
Return a string describing this object.
Definition: error.cc:93
Base class for field processors.
Definition: queryparser.h:749
InvalidArgumentError indicates an invalid parameter value was passed to the API.
Definition: error.h:241
InvalidOperationError indicates the API was used in an invalid way.
Definition: error.h:283
Class representing a list of search results.
Definition: mset.h:44
Xapian::doccount size() const
Return number of items in this MSet object.
Definition: omenquire.cc:318
Handle a number range.
Definition: queryparser.h:362
Handle a number range.
Definition: queryparser.h:681
Indicates a query string can't be parsed.
Definition: error.h:887
Build a Xapian::Query object from a user query string.
Definition: queryparser.h:797
void set_database(const Database &db)
Specify the database being searched.
Definition: queryparser.cc:142
void add_rangeprocessor(Xapian::RangeProcessor *range_proc, const std::string *grouping=NULL)
Register a RangeProcessor.
Definition: queryparser.cc:253
void set_stemmer(const Xapian::Stem &stemmer)
Set the stemmer.
Definition: queryparser.cc:85
void set_max_expansion(Xapian::termcount max_expansion, int max_type=Xapian::Query::WILDCARD_LIMIT_ERROR, unsigned flags=FLAG_WILDCARD|FLAG_PARTIAL)
Specify the maximum expansion of a wildcard and/or partial term.
Definition: queryparser.cc:147
void set_stemming_strategy(stem_strategy strategy)
Set the stemming strategy.
Definition: queryparser.cc:91
Query::op get_default_op() const
Get the current default operator.
Definition: queryparser.cc:136
TermIterator stoplist_end() const
End iterator over terms omitted from the query as stopwords.
Definition: queryparser.h:1298
void add_boolean_prefix(const std::string &field, const std::string &prefix, const std::string *grouping=NULL)
Add a boolean term prefix allowing the user to restrict a search with a boolean filter specified in t...
Definition: queryparser.cc:206
void add_valuerangeprocessor(Xapian::ValueRangeProcessor *vrproc)
Register a ValueRangeProcessor.
Definition: queryparser.h:1319
void set_default_op(Query::op default_op)
Set the default operator.
Definition: queryparser.cc:103
void set_stopper(const Stopper *stop=NULL)
Set the stopper.
Definition: queryparser.cc:97
TermIterator unstem_begin(const std::string &term) const
Begin iterator over unstemmed forms of the given stemmed query term.
Definition: queryparser.cc:240
std::string get_corrected_query_string() const
Get the spelling-corrected query string.
Definition: queryparser.cc:261
void add_prefix(const std::string &field, const std::string &prefix)
Add a free-text field term prefix.
Definition: queryparser.cc:184
Query parse_query(const std::string &query_string, unsigned flags=FLAG_DEFAULT, const std::string &default_prefix=std::string())
Parse a query.
Definition: queryparser.cc:162
@ FLAG_LOVEHATE
Support + and -.
Definition: queryparser.h:811
@ FLAG_AUTO_MULTIWORD_SYNONYMS
Enable automatic use of synonyms for single terms and groups of terms.
Definition: queryparser.h:890
@ FLAG_NGRAMS
Generate n-grams for scripts without explicit word breaks.
Definition: queryparser.h:914
@ FLAG_ACCUMULATE
Accumulate unstem and stoplist results.
Definition: queryparser.h:938
@ FLAG_DEFAULT
The default flags.
Definition: queryparser.h:958
@ FLAG_WILDCARD
Support wildcards.
Definition: queryparser.h:828
@ FLAG_SYNONYM
Enable synonym operator '~'.
Definition: queryparser.h:877
@ FLAG_SPELLING_CORRECTION
Enable spelling correction.
Definition: queryparser.h:871
@ FLAG_NO_POSITIONS
Produce a query which doesn't use positional information.
Definition: queryparser.h:949
@ FLAG_PHRASE
Support quoted phrases.
Definition: queryparser.h:809
@ FLAG_PARTIAL
Enable partial matching.
Definition: queryparser.h:856
@ FLAG_BOOLEAN
Support AND, OR, etc and bracketed subexpressions.
Definition: queryparser.h:807
TermIterator stoplist_begin() const
Begin iterator over terms omitted from the query as stopwords.
Definition: queryparser.cc:233
TermIterator unstem_end(const std::string &) const
End iterator over unstemmed forms of the given stemmed query term.
Definition: queryparser.h:1306
Class representing a query.
Definition: query.h:56
std::string get_description() const
Return a string describing this object.
Definition: query.cc:232
op
Query operators.
Definition: query.h:88
@ OP_SCALE_WEIGHT
Scale the weight contributed by a subquery.
Definition: query.h:176
@ OP_VALUE_RANGE
Match only documents where a value slot is within a given range.
Definition: query.h:168
@ OP_XOR
Match documents which an odd number of subqueries match.
Definition: query.h:117
@ OP_AND_MAYBE
Match the first subquery taking extra weight from other subqueries.
Definition: query.h:128
@ OP_NEAR
Match only documents where all subqueries match near each other.
Definition: query.h:150
@ OP_ELITE_SET
Pick the best N subqueries and combine with OP_OR.
Definition: query.h:225
@ OP_AND
Match only documents which all subqueries match.
Definition: query.h:94
@ OP_OR
Match documents which at least one subquery matches.
Definition: query.h:102
@ OP_FILTER
Match like OP_AND but only taking weight from the first subquery.
Definition: query.h:138
@ OP_PHRASE
Match only documents where all subqueries match near and in order.
Definition: query.h:162
@ OP_VALUE_LE
Match only documents where a value slot is <= a given value.
Definition: query.h:241
@ OP_SYNONYM
Match like OP_OR but weighting as if a single term.
Definition: query.h:249
@ OP_AND_NOT
Match documents which the first subquery matches but no others do.
Definition: query.h:109
@ OP_VALUE_GE
Match only documents where a value slot is >= a given value.
Definition: query.h:233
static const Xapian::Query MatchAll
A query matching all documents.
Definition: query.h:85
Base class for range processors.
Definition: queryparser.h:141
virtual Xapian::Query operator()(const std::string &begin, const std::string &end)
Check for a valid range of this type.
Simple implementation of Stopper class - this will suit most users.
Definition: queryparser.h:100
void add(const std::string &word)
Add a single stop word.
Definition: queryparser.h:124
Class representing a stemming algorithm.
Definition: stem.h:62
Handle a string range.
Definition: queryparser.h:485
Class for iterating over a list of terms.
Definition: termiterator.h:41
UnimplementedError indicates an attempt to use an unimplemented feature.
Definition: error.h:325
Base class for value range processors.
Definition: queryparser.h:424
WildcardError indicates an error expanding a wildcarded query.
Definition: error.h:1013
This class provides read/write access to a database.
Definition: database.h:795
void commit()
Commit any pending modifications made to the database.
Definition: omdatabase.cc:848
void add_spelling(const std::string &word, Xapian::termcount freqinc=1) const
Add a word to the spelling dictionary.
Definition: omdatabase.cc:995
Xapian::docid add_document(const Xapian::Document &document)
Add a new document to the database.
Definition: omdatabase.cc:893
void add_synonym(const std::string &term, const std::string &synonym) const
Add a synonym for a term.
Definition: omdatabase.cc:1019
Measure CPU time.
static const test_desc tests[]
The lists of tests to perform.
string str(int value)
Convert int to std::string.
Definition: str.cc:90
unsigned tolower(unsigned ch)
Convert a Unicode character to lowercase.
Definition: unicode.h:376
The Xapian namespace contains public interfaces for the Xapian library.
Definition: compactor.cc:80
const valueno BAD_VALUENO
Reserved value to indicate "no valueno".
Definition: types.h:125
std::string sortable_serialise(double value)
Convert a floating point number to a string, preserving sort order.
Definition: queryparser.h:1401
unsigned XAPIAN_TERMCOUNT_BASE_TYPE termcount
A counts of terms.
Definition: types.h:72
@ RP_DATE_PREFER_MDY
Definition: queryparser.h:136
@ RP_REPEATED
Definition: queryparser.h:135
@ RP_SUFFIX
Definition: queryparser.h:134
unsigned valueno
The number for a value slot in a document.
Definition: types.h:108
static Xapian::Stem stemmer
Definition: stemtest.cc:42
Convert types to std::string.
Various handy helpers which std::string really should provide.
bool startswith(const std::string &s, char pfx)
Definition: stringutils.h:51
char C_tolower(char ch)
Definition: stringutils.h:221
Xapian::Query operator()(const std::string &b, const std::string &e) override
Check for a valid range of this type.
Xapian::valueno operator()(std::string &begin, std::string &end)
Check for a valid range of this type.
Xapian::Query::op op
const char * query
const char * expect
#define TEST_REL(A, REL, B)
Test a relation holds,e.g. TEST_REL(a,>,b);.
Definition: testmacros.h:36
std::ostringstream tout
The debug printing stream.
Definition: testsuite.cc:104
a generic test suite engine
#define FAIL_TEST(MSG)
Fail the current testcase with message MSG.
Definition: testsuite.h:68
#define SKIP_TEST(MSG)
Skip the current testcase with message MSG.
Definition: testsuite.h:74
#define TEST_EQUAL(a, b)
Test for equality of two things.
Definition: testsuite.h:278
#define TEST_STRINGS_EQUAL(a, b)
Test for equality of two strings.
Definition: testsuite.h:287
#define TEST(a)
Test a condition, without an additional explanation for failure.
Definition: testsuite.h:275
Xapian-specific test helper functions and macros.
#define TEST_EXCEPTION(TYPE, CODE)
Check that CODE throws exactly Xapian exception TYPE.
Definition: testutils.h:109
#define TEST_MSET_SIZE(M, S)
Check MSet M has size S.
Definition: testutils.h:78
Public interfaces for the Xapian library.