source: trunk/gsdl/src/colservr/queryfilter.cpp@ 396

Last change on this file since 396 was 396, checked in by sjboddie, 25 years ago

got using phrasesearch for post-processing

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 22.6 KB
Line 
1/**********************************************************************
2 *
3 * queryfilter.cpp --
4 * Copyright (C) 1999 The New Zealand Digital Library Project
5 *
6 * PUT COPYRIGHT NOTICE HERE
7 *
8 * $Id: queryfilter.cpp 396 1999-07-16 00:17:06Z sjboddie $
9 *
10 *********************************************************************/
11
12/*
13 $Log$
14 Revision 1.13 1999/07/16 00:17:06 sjboddie
15 got using phrasesearch for post-processing
16
17 Revision 1.12 1999/07/09 02:19:43 rjmcnab
18 Fixed a couple of compiler conflicts
19
20 Revision 1.11 1999/07/08 20:49:44 rjmcnab
21 Added result_num to the ResultDocInto_t structure.
22
23 Revision 1.10 1999/07/07 06:19:46 rjmcnab
24 Added ability to combine two or more independant queries.
25
26 Revision 1.9 1999/07/01 09:29:20 rjmcnab
27 Changes for better reporting of number documents which match a query. Changes
28 should still work as before with older versions of mg.
29
30 Revision 1.8 1999/07/01 03:59:54 rjmcnab
31 reduced MAXDOCS to 200 (more reasonable ???). I also added a virtual
32 method for post-processing the query.
33
34 Revision 1.7 1999/06/30 04:04:13 rjmcnab
35 made stemming functions available from mgsearch and made the stems
36 for the query terms available in queryinfo
37
38 Revision 1.6 1999/06/29 22:06:23 rjmcnab
39 Added a couple of fields to queryinfo to handle a special version
40 of mg.
41
42 Revision 1.5 1999/06/27 22:08:48 sjboddie
43 now check for defaultindex, defaultsubcollection, and defaultlanguage
44 entries in config files
45
46 Revision 1.4 1999/06/16 02:03:25 sjboddie
47 fixed bug in isApprox and set MAXDOCS to always be 500
48
49 Revision 1.3 1999/04/19 23:56:09 rjmcnab
50 Finished the gdbm metadata stuff
51
52 Revision 1.2 1999/04/12 03:45:03 rjmcnab
53 Finished the query filter.
54
55 Revision 1.1 1999/04/06 22:22:09 rjmcnab
56 Initial revision.
57
58 */
59
60
61#include "queryfilter.h"
62#include "fileutil.h"
63#include "queryinfo.h"
64#include "phrasesearch.h"
65#include <assert.h>
66
67#define MAXDOCS 200 // note that maxdocs must be at least as large
68 // as the highest possible value of EndResults
69
70// some useful functions
71
72// translate will return true if successful
73static bool translate (gdbmclass *gdbmptr, int docnum, text_t &trans_OID) {
74 infodbclass info;
75
76 trans_OID.clear();
77
78 // get the info
79 if (gdbmptr == NULL) return false;
80 if (!gdbmptr->getinfo(docnum, info)) return false;
81
82 // translate
83 if (info["section"].empty()) return false;
84
85 trans_OID = info["section"];
86 return true;
87}
88
89
90// whether document results are needed
91static bool need_matching_docs (int filterResultOptions) {
92 return ((filterResultOptions & FROID) || (filterResultOptions & FRranking) ||
93 (filterResultOptions & FRmetadata));
94}
95
96// whether term information is needed
97static bool need_term_info (int filterResultOptions) {
98 return ((filterResultOptions & FRtermFreq) || (filterResultOptions & FRmatchTerms));
99}
100
101///////////////////////////////
102// methods for resultsorderer_t
103///////////////////////////////
104
105resultsorderer_t::resultsorderer_t() {
106 clear ();
107}
108
109void resultsorderer_t::clear() {
110 compare_phrase_match = false;
111 compare_terms_match = false;
112 compare_doc_weight = true;
113
114 docset = NULL;
115}
116
117bool resultsorderer_t::operator()(const int &t1, const int &t2) const {
118 if (docset == NULL) return t1>t2;
119
120 docresultmap::iterator t1_here = docset->find(t1);
121 docresultmap::iterator t2_here = docset->find(t2);
122 docresultmap::iterator end = docset->end();
123
124 // sort all the document numbers not in the document set to
125 // the end of the list
126 if (t1_here == end) {
127 if (t2_here == end) return t1>t2;
128 else return true;
129 } else if (t2_here == end) return false;
130
131 if (compare_phrase_match) {
132 if ((*t1_here).second.num_phrase_match > (*t2_here).second.num_phrase_match) return true;
133 if ((*t1_here).second.num_phrase_match < (*t2_here).second.num_phrase_match) return false;
134 }
135
136 if (compare_terms_match) {
137 if ((*t1_here).second.num_query_terms_matched > (*t2_here).second.num_query_terms_matched) return true;
138 if ((*t1_here).second.num_query_terms_matched < (*t2_here).second.num_query_terms_matched) return false;
139 }
140
141 if (compare_doc_weight) {
142 if ((*t1_here).second.docweight > (*t2_here).second.docweight) return true;
143 if ((*t1_here).second.docweight < (*t2_here).second.docweight) return false;
144 }
145
146 return t1>t2;
147}
148
149
150
151
152/////////////////////////////////
153// functions for queryfilterclass
154/////////////////////////////////
155
156// loads up phrases data structure with any phrases (that's the quoted bits)
157// occuring in the querystring
158void queryfilterclass::get_phrase_terms (const text_t &querystring,
159 const termfreqclassarray &orgterms,
160 vector<termfreqclassarray> &phrases) {
161
162 text_t::const_iterator here = querystring.begin();
163 text_t::const_iterator end = querystring.end();
164
165 termfreqclassarray tmpterms;
166
167 int termcount = 0;
168 bool foundquote = false;
169 bool foundbreak = false;
170 bool start = true;
171 while (here != end) {
172 if (*here == '\"') {
173 if (foundquote) {
174 if (!foundbreak && !start) {
175 tmpterms.push_back (orgterms[termcount]);
176 termcount ++;
177 }
178 if (tmpterms.size() > 1) {
179 phrases.push_back (tmpterms);
180 tmpterms.erase (tmpterms.begin(), tmpterms.end());
181 }
182 foundquote = false;
183 foundbreak = true;
184 } else foundquote = true;
185 } else if (!is_unicode_letdig(*here)) {
186 // found a break between terms
187 if (!foundbreak && !start) {
188 if (foundquote)
189 tmpterms.push_back (orgterms[termcount]);
190 termcount ++;
191 }
192 foundbreak = true;
193 } else {
194 start = false;
195 foundbreak = false;
196 }
197 here++;
198 }
199}
200
201// do aditional query processing
202void queryfilterclass::post_process (const queryparamclass &queryparams,
203 queryresultsclass &queryresults) {
204
205 // post-process the results if needed
206 if (queryresults.orgterms.size() > 1 && !queryresults.docs.docset.empty()) {
207
208 // get the terms between quotes (if any)
209 vector<termfreqclassarray> phrases;
210 get_phrase_terms (queryparams.querystring, queryresults.orgterms, phrases);
211
212 if (phrases.size() > 0) {
213
214 // get the long version of the index
215 text_t longindex;
216 indexmap.to2from (queryparams.index, longindex);
217
218 vector<termfreqclassarray>::const_iterator this_phrase = phrases.begin();
219 vector<termfreqclassarray>::const_iterator end_phrase = phrases.end();
220
221 while (this_phrase != end_phrase) {
222
223 // process each of the matched documents
224 docresultmap::iterator docs_here = queryresults.docs.docset.begin();
225 docresultmap::iterator docs_end = queryresults.docs.docset.end();
226 while (docs_here != docs_end) {
227 if (OID_phrase_search (*mgsearchptr, *gdbmptr, queryparams.index,
228 queryparams.subcollection, queryparams.language,
229 longindex, queryparams.collection, *this_phrase,
230 (*docs_here).second.docnum)) {
231 (*docs_here).second.num_phrase_match++;
232 }
233
234 docs_here++;
235 }
236 this_phrase++;
237 }
238 }
239 }
240}
241
242// get the query parameters
243void queryfilterclass::parse_query_params (const FilterRequest_t &request,
244 vector<queryparamclass> &query_params,
245 int &startresults,
246 int &endresults,
247 ostream &logout) {
248 outconvertclass text_t2ascii;
249
250 // set defaults for the return parameters
251 query_params.erase(query_params.begin(), query_params.end());
252 startresults = filterOptions["StartResults"].defaultValue.getint();
253 endresults = filterOptions["EndResults"].defaultValue.getint();
254
255 // set defaults for query parameters
256 queryparamclass query;
257 query.combinequery = "or"; // first one must be "or"
258 query.collection = collection;
259 query.index = filterOptions["Index"].defaultValue;
260 query.subcollection = filterOptions["Subcollection"].defaultValue;
261 query.language = filterOptions["Language"].defaultValue;
262 query.querystring.clear();
263 query.search_type = (filterOptions["QueryType"].defaultValue == "ranked");
264 query.casefolding = (filterOptions["Casefold"].defaultValue == "true");
265 query.stemming = (filterOptions["Stem"].defaultValue == "true");
266 query.maxdocs = MAXDOCS; // default for single query
267
268 OptionValue_tarray::const_iterator options_here = request.filterOptions.begin();
269 OptionValue_tarray::const_iterator options_end = request.filterOptions.end();
270 while (options_here != options_end) {
271 if ((*options_here).name == "CombineQuery") {
272 // add this query
273
274 // "all", needed when combining queries where the document results are needed
275 if (need_matching_docs (request.filterResultOptions)) query.maxdocs = -1;
276 query_params.push_back (query);
277
278 // start on next query
279 query.clear();
280 query.combinequery = (*options_here).value;
281
282 // set defaults for query parameters
283 query.collection = collection;
284 query.index = filterOptions["Index"].defaultValue;
285 query.subcollection = filterOptions["Subcollection"].defaultValue;
286 query.language = filterOptions["Language"].defaultValue;
287 query.querystring.clear();
288 query.search_type = (filterOptions["QueryType"].defaultValue == "ranked");
289 query.casefolding = (filterOptions["Casefold"].defaultValue == "true");
290 query.stemming = (filterOptions["Stem"].defaultValue == "true");
291
292 // "all", needed when combining queries where the document results are needed
293 if (need_matching_docs (request.filterResultOptions)) query.maxdocs = -1;
294 else query.maxdocs = MAXDOCS; // "all"
295
296 } else if ((*options_here).name == "StartResults") {
297 startresults = (*options_here).value.getint();
298 } else if ((*options_here).name == "EndResults") {
299 endresults = (*options_here).value.getint();
300 } else if ((*options_here).name == "QueryType") {
301 query.search_type = ((*options_here).value == "ranked");
302 } else if ((*options_here).name == "Term") {
303 query.querystring = (*options_here).value;
304 } else if ((*options_here).name == "Casefold") {
305 query.casefolding = ((*options_here).value == "true");
306 } else if ((*options_here).name == "Stem") {
307 query.stemming = ((*options_here).value == "true");
308 } else if ((*options_here).name == "Index") {
309 query.index = (*options_here).value;
310 } else if ((*options_here).name == "Subcollection") {
311 query.subcollection = (*options_here).value;
312 } else if ((*options_here).name == "Language") {
313 query.language = (*options_here).value;
314 } else {
315 logout << text_t2ascii
316 << "warning: unknown queryfilter option \""
317 << (*options_here).name
318 << "\" ignored.\n\n";
319 }
320
321 options_here++;
322 }
323
324 // add the last query
325 query_params.push_back (query);
326}
327
328
329
330// do query that might involve multiple sub queries
331// mgsearchptr and gdbmptr are assumed to be valid
332void queryfilterclass::do_multi_query (const FilterRequest_t &request,
333 const vector<queryparamclass> &query_params,
334 queryresultsclass &multiresults,
335 comerror_t &err, ostream &logout) {
336 outconvertclass text_t2ascii;
337
338 err = noError;
339 mgsearchptr->setcollectdir (collectdir);
340 multiresults.clear();
341
342 vector<queryparamclass>::const_iterator query_here = query_params.begin();
343 vector<queryparamclass>::const_iterator query_end = query_params.end();
344 while (query_here != query_end) {
345 queryresultsclass thisqueryresults;
346
347 if (!mgsearchptr->search(*query_here, thisqueryresults)) {
348 // most likely a system problem
349 logout << text_t2ascii
350 << "system problem: could not do search with mg for index \""
351 << (*query_here).index << (*query_here).subcollection
352 << (*query_here).language << "\".\n\n";
353 err = systemProblem;
354 return;
355 }
356
357 // combine the results
358 if (need_matching_docs (request.filterResultOptions)) {
359 // post-process the results if needed
360 if (!thisqueryresults.postprocessed && thisqueryresults.orgterms.size() > 1 &&
361 !thisqueryresults.docs.docset.empty()) {
362 post_process (*query_here, thisqueryresults);
363 thisqueryresults.postprocessed = true;
364 multiresults.postprocessed = true;
365 }
366
367 if (query_params.size() == 1) {
368 multiresults.docs = thisqueryresults.docs; // just one set of results
369 multiresults.docs_matched = thisqueryresults.docs_matched;
370 multiresults.is_approx = thisqueryresults.is_approx;
371
372 } else {
373 if ((*query_here).combinequery == "and") {
374 multiresults.docs.combine_and (thisqueryresults.docs);
375 } else if ((*query_here).combinequery == "or") {
376 multiresults.docs.combine_or (thisqueryresults.docs);
377 } else if ((*query_here).combinequery == "not") {
378 multiresults.docs.combine_not (thisqueryresults.docs);
379 }
380 multiresults.docs_matched = multiresults.docs.docset.size();
381 multiresults.is_approx = false;
382 }
383 }
384
385 // combine the term information
386 if (need_term_info (request.filterResultOptions)) {
387 // append the terms
388 multiresults.orgterms.insert(multiresults.orgterms.end(),
389 thisqueryresults.orgterms.begin(),
390 thisqueryresults.orgterms.end());
391
392 // add the term variants
393 text_tset::iterator termvar_here = thisqueryresults.termvariants.begin();
394 text_tset::iterator termvar_end = thisqueryresults.termvariants.end();
395 while (termvar_here != termvar_end) {
396 multiresults.termvariants.insert(*termvar_here);
397 termvar_here++;
398 }
399 }
400
401 query_here++;
402 }
403
404 // sort and unique the query terms
405 multiresults.sortuniqqueryterms ();
406}
407
408
409void queryfilterclass::sort_doc_results (const FilterRequest_t &/*request*/,
410 docresultsclass &docs) {
411 resultsorderer_t resultsorderer;
412 resultsorderer.compare_phrase_match = true;
413 resultsorderer.docset = &(docs.docset);
414
415 // first get a list of document numbers
416 docs.docnum_order();
417
418 sort (docs.docorder.begin(), docs.docorder.end(), resultsorderer);
419}
420
421
422
423queryfilterclass::queryfilterclass () {
424 gdbmptr = NULL;
425 mgsearchptr = NULL;
426
427 FilterOption_t filtopt;
428 filtopt.name = "CombineQuery";
429 filtopt.type = FilterOption_t::enumeratedt;
430 filtopt.repeatable = FilterOption_t::onePerQuery;
431 filtopt.defaultValue = "and";
432 filtopt.validValues.push_back("and");
433 filtopt.validValues.push_back("or");
434 filtopt.validValues.push_back("not");
435 filterOptions["CombineQuery"] = filtopt;
436
437 // -- onePerQuery StartResults integer
438 filtopt.clear();
439 filtopt.name = "StartResults";
440 filtopt.type = FilterOption_t::integert;
441 filtopt.repeatable = FilterOption_t::onePerQuery;
442 filtopt.defaultValue = "1";
443 filtopt.validValues.push_back("1");
444 filtopt.validValues.push_back("1000");
445 filterOptions["StartResults"] = filtopt;
446
447 // -- onePerQuery EndResults integer
448 filtopt.clear();
449 filtopt.name = "EndResults";
450 filtopt.type = FilterOption_t::integert;
451 filtopt.repeatable = FilterOption_t::onePerQuery;
452 filtopt.defaultValue = "10";
453 filtopt.validValues.push_back("1");
454 filtopt.validValues.push_back("1000");
455 filterOptions["EndResults"] = filtopt;
456
457 // -- onePerQuery QueryType enumerated (boolean, ranked)
458 filtopt.clear();
459 filtopt.name = "QueryType";
460 filtopt.type = FilterOption_t::enumeratedt;
461 filtopt.repeatable = FilterOption_t::onePerQuery;
462 filtopt.defaultValue = "ranked";
463 filtopt.validValues.push_back("boolean");
464 filtopt.validValues.push_back("ranked");
465 filterOptions["QueryType"] = filtopt;
466
467 // -- onePerTerm Term string ???
468 filtopt.clear();
469 filtopt.name = "Term";
470 filtopt.type = FilterOption_t::stringt;
471 filtopt.repeatable = FilterOption_t::onePerTerm;
472 filtopt.defaultValue = "";
473 filterOptions["Term"] = filtopt;
474
475 // -- onePerTerm Casefold boolean
476 filtopt.clear();
477 filtopt.name = "Casefold";
478 filtopt.type = FilterOption_t::booleant;
479 filtopt.repeatable = FilterOption_t::onePerTerm;
480 filtopt.defaultValue = "true";
481 filtopt.validValues.push_back("false");
482 filtopt.validValues.push_back("true");
483 filterOptions["Casefold"] = filtopt;
484
485 // -- onePerTerm Stem boolean
486 filtopt.clear();
487 filtopt.name = "Stem";
488 filtopt.type = FilterOption_t::booleant;
489 filtopt.repeatable = FilterOption_t::onePerTerm;
490 filtopt.defaultValue = "false";
491 filtopt.validValues.push_back("false");
492 filtopt.validValues.push_back("true");
493 filterOptions["Stem"] = filtopt;
494
495 // -- onePerTerm Index enumerated
496 filtopt.clear();
497 filtopt.name = "Index";
498 filtopt.type = FilterOption_t::enumeratedt;
499 filtopt.repeatable = FilterOption_t::onePerTerm;
500 filtopt.defaultValue = "";
501 filterOptions["Index"] = filtopt;
502
503 // -- onePerTerm Subcollection enumerated
504 filtopt.clear();
505 filtopt.name = "Subcollection";
506 filtopt.type = FilterOption_t::enumeratedt;
507 filtopt.repeatable = FilterOption_t::onePerTerm;
508 filtopt.defaultValue = "";
509 filterOptions["Subcollection"] = filtopt;
510
511 // -- onePerTerm Language enumerated
512 filtopt.clear();
513 filtopt.name = "Language";
514 filtopt.type = FilterOption_t::enumeratedt;
515 filtopt.repeatable = FilterOption_t::onePerTerm;
516 filtopt.defaultValue = "";
517 filterOptions["Language"] = filtopt;
518}
519
520queryfilterclass::~queryfilterclass () {
521}
522
523void queryfilterclass::configure (const text_t &key, const text_tarray &cfgline) {
524 filterclass::configure (key, cfgline);
525
526 if (key == "indexmap") {
527 indexmap.importmap (cfgline);
528
529 // update the list of indexes in the filter information
530 text_tarray options;
531 indexmap.gettoarray (options);
532 filterOptions["Index"].validValues = options;
533
534 } else if (key == "defaultindex") {
535 indexmap.from2to (cfgline[0], filterOptions["Index"].defaultValue);
536
537 } else if (key == "subcollectionmap") {
538 subcollectionmap.importmap (cfgline);
539
540 // update the list of subcollections in the filter information
541 text_tarray options;
542 subcollectionmap.gettoarray (options);
543 filterOptions["Subcollection"].validValues = options;
544
545 } else if (key == "defaultsubcollection") {
546 subcollectionmap.from2to (cfgline[0], filterOptions["Subcollection"].defaultValue);
547
548 } else if (key == "languagemap") {
549 languagemap.importmap (cfgline);
550
551 // update the list of languages in the filter information
552 text_tarray options;
553 languagemap.gettoarray (options);
554 filterOptions["Language"].validValues = options;
555
556 } else if (key == "defaultlanguage")
557 languagemap.from2to (cfgline[0], filterOptions["Language"].defaultValue);
558}
559
560bool queryfilterclass::init (ostream &logout) {
561 outconvertclass text_t2ascii;
562
563 if (!filterclass::init(logout)) return false;
564
565 // get the filename for the database and make sure it exists
566
567
568 // yet another hack for niupepa
569 if (collection == "niupepa")
570 gdbm_filename = filename_cat(collectdir,"index.new","text",collection);
571 else
572 gdbm_filename = filename_cat(collectdir,"index","text",collection);
573
574#ifdef _LITTLE_ENDIAN
575 gdbm_filename += ".ldb";
576#else
577 gdbm_filename += ".bdb";
578#endif
579 if (!file_exists(gdbm_filename)) {
580 logout << text_t2ascii
581 << "error: gdbm database \""
582 << gdbm_filename << "\" does not exist\n\n";
583 return false;
584 }
585
586 return true;
587}
588
589void queryfilterclass::filter (const FilterRequest_t &request,
590 FilterResponse_t &response,
591 comerror_t &err, ostream &logout) {
592 outconvertclass text_t2ascii;
593
594 response.clear ();
595 err = noError;
596 if (gdbmptr == NULL) {
597 // most likely a configuration problem
598 logout << text_t2ascii
599 << "configuration error: queryfilter contains a null gdbmclass\n\n";
600 err = configurationError;
601 return;
602 }
603 if (mgsearchptr == NULL) {
604 // most likely a configuration problem
605 logout << text_t2ascii
606 << "configuration error: queryfilter contains a null mgsearchclass\n\n";
607 err = configurationError;
608 return;
609 }
610
611 // open the database
612 gdbmptr->setlogout(&logout);
613 if (!gdbmptr->opendatabase (gdbm_filename)) {
614 // most likely a system problem (we have already checked that the
615 // gdbm database exists)
616 logout << text_t2ascii
617 << "system problem: open on gdbm database \""
618 << gdbm_filename << "\" failed\n\n";
619 err = systemProblem;
620 return;
621 }
622
623 // get the query parameters
624 int startresults = filterOptions["StartResults"].defaultValue.getint();
625 int endresults = filterOptions["EndResults"].defaultValue.getint();
626 vector<queryparamclass> queryfilterparams;
627 parse_query_params (request, queryfilterparams, startresults, endresults, logout);
628
629 // do query
630 queryresultsclass queryresults;
631 do_multi_query (request, queryfilterparams, queryresults, err, logout);
632 if (err != noError) return;
633
634 // assemble document results
635 if (need_matching_docs (request.filterResultOptions)) {
636 // sort the query results
637 sort_doc_results (request, queryresults.docs);
638
639 int resultnum = 1;
640 ResultDocInfo_t resultdoc;
641 text_t trans_OID;
642 vector<int>::iterator docorder_here = queryresults.docs.docorder.begin();
643 vector<int>::iterator docorder_end = queryresults.docs.docorder.end();
644
645 while (docorder_here != docorder_end) {
646 if (resultnum > endresults) break;
647
648 // translate the document number
649 if (!translate(gdbmptr, *docorder_here, trans_OID)) {
650 logout << text_t2ascii
651 << "warning: could not translate mg document number \""
652 << *docorder_here << "\"to OID.\n\n";
653
654 } else {
655 docresultmap::iterator docset_here = queryresults.docs.docset.find (*docorder_here);
656
657 // see if there is a result for this number,
658 // if it is in the request set (or the request set is empty)
659 if (docset_here != queryresults.docs.docset.end() &&
660 (request.docSet.empty() || in_set(request.docSet, trans_OID))) {
661 if (resultnum >= startresults) {
662 // add this document
663 resultdoc.OID = trans_OID;
664 resultdoc.result_num = resultnum;
665 resultdoc.ranking = (int)((*docset_here).second.docweight * 10000.0 + 0.5);
666
667 // these next two are not available on all versions of mg
668 resultdoc.num_terms_matched = (*docset_here).second.num_query_terms_matched;
669 resultdoc.num_phrase_match = (*docset_here).second.num_phrase_match;
670
671 response.docInfo.push_back (resultdoc);
672 }
673
674 resultnum++;
675 }
676 }
677
678 docorder_here++;
679 }
680 }
681
682 // assemble the term results
683 if (need_term_info(request.filterResultOptions)) {
684 // note: the terms have already been sorted and uniqued
685
686 TermInfo_t terminfo;
687 bool terms_first = true;
688 termfreqclassarray::iterator terms_here = queryresults.terms.begin();
689 termfreqclassarray::iterator terms_end = queryresults.terms.end();
690
691 while (terms_here != terms_end) {
692 terminfo.clear();
693 terminfo.term = (*terms_here).termstr;
694 terminfo.freq = (*terms_here).termfreq;
695 if (terms_first) {
696 text_tset::iterator termvariants_here = queryresults.termvariants.begin();
697 text_tset::iterator termvariants_end = queryresults.termvariants.end();
698 while (termvariants_here != termvariants_end) {
699 terminfo.matchTerms.push_back (*termvariants_here);
700 termvariants_here++;
701 }
702 }
703 terms_first = false;
704
705 response.termInfo.push_back (terminfo);
706
707 terms_here++;
708 }
709 }
710
711 response.numDocs = queryresults.docs_matched;
712 response.isApprox = queryresults.is_approx;
713}
Note: See TracBrowser for help on using the repository browser.