1 /** @file soaktest_queries.cc
2 * @brief Soaktest generating lots of random queries.
3 */
4 /* Copyright (C) 2010 Richard Boulton
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
19 * USA
20 */
21
22 #include <config.h>
23
24 #include "soaktest/soaktest.h"
25 #include "soaktest/soaktest_queries.h"
26
27 #include <xapian.h>
28
29 #include "backendmanager.h"
30 #include "str.h"
31 #include "testrunner.h"
32 #include "testsuite.h"
33 #include "testutils.h"
34 #include "utils.h"
35
36 #include <list>
37
38 using namespace std;
39
40 /** Make a database in which docs have the fields:
41 *
42 * - N: values are numbers between 1 and maxtermsperfield. Each document
43 * contains all Nx for x from 1 to randint(maxtermsperfield).
44 */
45 static void
builddb_queries1(Xapian::WritableDatabase & db,const string & arg)46 builddb_queries1(Xapian::WritableDatabase &db, const string &arg)
47 {
48 unsigned int doccount = 1000;
49 unsigned int maxtermsperfield = atoi(arg.c_str());
50 for (unsigned int i = 0; i < doccount; ++i) {
51 Xapian::Document doc;
52 for (unsigned int j = randint(maxtermsperfield) + 1; j != 0; --j) {
53 doc.add_term("N" + str(j));
54 }
55 db.add_document(doc);
56 }
57 db.commit();
58 }
59
60 /// The environment used by the steps when building a query.
61 struct QueryBuilderEnv {
62 /// Workspace for the query builder steps.
63 list<Xapian::Query> pieces;
64
65 unsigned int maxtermsperfield;
66 unsigned int maxchildren;
67
QueryBuilderEnvQueryBuilderEnv68 QueryBuilderEnv(unsigned int maxtermsperfield_,
69 unsigned int maxchildren_)
70 : maxtermsperfield(maxtermsperfield_),
71 maxchildren(maxchildren_)
72 {}
73
74 /// Pop a query from the front of the list of pieces, and return it.
popQueryBuilderEnv75 Xapian::Query pop() {
76 if (pieces.empty()) return Xapian::Query();
77 Xapian::Query result = pieces.front();
78 pieces.pop_front();
79 return result;
80 }
81
82 /// Get an iterator pointing to the "num"th element in pieces.
pickQueryBuilderEnv83 list<Xapian::Query>::iterator pick(unsigned int num) {
84 list<Xapian::Query>::iterator i = pieces.begin();
85 for (unsigned int c = 0; c != num && i != pieces.end(); ++c, ++i) {}
86 return i;
87 }
88 };
89
90 typedef void (*QueryStep)(QueryBuilderEnv &);
91
92 /// Push a leaf query on field N onto the list of query pieces.
push_leaf_N(QueryBuilderEnv & env)93 static void push_leaf_N(QueryBuilderEnv & env)
94 {
95 env.pieces.push_back(Xapian::Query(
96 "N" + str(randint(env.maxtermsperfield) + 1)));
97 }
98
99 /** Combine some queries with OR.
100 *
101 * The queries are removed from the list and the combined query is added to
102 * the end.
103 */
combine_OR(QueryBuilderEnv & env)104 static void combine_OR(QueryBuilderEnv & env)
105 {
106 list<Xapian::Query>::iterator i = env.pick(randint(env.maxchildren));
107 Xapian::Query combined(Xapian::Query::OP_OR, env.pieces.begin(), i);
108 env.pieces.erase(env.pieces.begin(), i);
109 env.pieces.push_back(combined);
110 }
111
112 /** Combine some queries with AND.
113 *
114 * The queries are removed from the list and the combined query is added to
115 * the end.
116 */
combine_AND(QueryBuilderEnv & env)117 static void combine_AND(QueryBuilderEnv & env)
118 {
119 list<Xapian::Query>::iterator i = env.pick(randint(env.maxchildren));
120 Xapian::Query combined(Xapian::Query::OP_AND, env.pieces.begin(), i);
121 env.pieces.erase(env.pieces.begin(), i);
122 env.pieces.push_back(combined);
123 }
124
125 /** Combine some queries with XOR.
126 *
127 * The queries are removed from the list and the combined query is added to
128 * the end.
129 */
combine_XOR(QueryBuilderEnv & env)130 static void combine_XOR(QueryBuilderEnv & env)
131 {
132 list<Xapian::Query>::iterator i = env.pick(randint(env.maxchildren));
133 Xapian::Query combined(Xapian::Query::OP_XOR, env.pieces.begin(), i);
134 env.pieces.erase(env.pieces.begin(), i);
135 env.pieces.push_back(combined);
136 }
137
138 /** Combine some queries with AND_NOT.
139 *
140 * The queries are removed from the list and the combined query is added to
141 * the end.
142 */
combine_NOT(QueryBuilderEnv & env)143 static void combine_NOT(QueryBuilderEnv & env)
144 {
145 if (env.pieces.size() < 2) return;
146 list<Xapian::Query>::iterator i = env.pick(2);
147 Xapian::Query combined(Xapian::Query::OP_AND_NOT, env.pieces.begin(), i);
148 env.pieces.erase(env.pieces.begin(), i);
149 env.pieces.push_back(combined);
150 }
151
152 /// Random query builder.
153 class QueryBuilder {
154 /// The possible steps.
155 vector<QueryStep> options;
156
157 /// The environment for the build steps.
158 QueryBuilderEnv env;
159
160 /// Maximum number of steps to take when building a query.
161 unsigned int maxsteps;
162
163 public:
QueryBuilder(unsigned int maxtermsperfield_,unsigned int maxchildren_,unsigned int maxsteps_)164 QueryBuilder(unsigned int maxtermsperfield_,
165 unsigned int maxchildren_,
166 unsigned int maxsteps_)
167 : env(maxtermsperfield_, maxchildren_),
168 maxsteps(maxsteps_)
169 {
170 // Build up the set of options.
171 // Some options are added multiple times to make them more likely.
172 options.push_back(push_leaf_N);
173 options.push_back(push_leaf_N);
174 options.push_back(push_leaf_N);
175 options.push_back(push_leaf_N);
176 options.push_back(combine_OR);
177 options.push_back(combine_AND);
178 options.push_back(combine_XOR);
179 options.push_back(combine_NOT);
180 }
181
182 /** Build a random query.
183 *
184 * This performs a random number of steps, each of which modifies the
185 * QueryBuilderEnv by picking a random one of the options.
186 *
187 * After the steps have been performed, the first item on the list in
188 * QueryBuilderEnv is popped and returned.
189 */
make_query()190 Xapian::Query make_query() {
191 unsigned int steps = randint(maxsteps) + 1;
192 while (steps-- != 0) {
193 QueryStep & step = options[randint(options.size())];
194 step(env);
195 }
196 return env.pop();
197 }
198 };
199
200 // Generate a load of random queries, and run them checking that the first
201 // returned result is the same when asking for 1 result as it is when asking
202 // for all results. This is a basic test that early-termination query
203 // optimisations aren't causing different results to be returned.
204 DEFINE_TESTCASE(queries1, writable && !remote && !inmemory) {
205 unsigned int seed = initrand();
206 unsigned int maxtermsperfield = 100;
207 unsigned int repetitions = 10000;
208 QueryBuilder builder(maxtermsperfield, 10, 10);
209
210 Xapian::Database db;
211 string arg(str(maxtermsperfield));
212 db = backendmanager->get_database("queries1_" + str(seed) + "_" + arg,
213 builddb_queries1, arg);
214
215 // Reset the random seed, to make results repeatable whether database was
216 // created or not.
217 initrand();
218
219 Xapian::Enquire enquire(db);
220
221 unsigned int count = 0;
222 while (++count != repetitions) {
223 Xapian::Query query(builder.make_query());
224 tout.str(string());
225 tout << "query " << count << ": " << query << "\n";
226
227 enquire.set_query(query);
228 Xapian::MSet mset1 = enquire.get_mset(0, 1);
229 Xapian::MSet mset10 = enquire.get_mset(0, 10);
230 Xapian::MSet msetall = enquire.get_mset(0, db.get_doccount());
231 tout << mset1 << "\n";
232 tout << mset10 << "\n";
233 tout << msetall << "\n";
234 if (mset1.empty()) {
235 TEST(mset10.empty());
236 TEST(msetall.empty());
237 continue;
238 }
239 TEST(mset_range_is_same(mset1, 0, msetall, 0, mset1.size()));
240 TEST(mset_range_is_same(mset10, 0, msetall, 0, mset10.size()));
241 }
242
243 return true;
244 }
245