UNPKG

19.2 kBJavaScriptView Raw
1/**
2 * Load dependencies
3 */
4var querystring = require('querystring'),
5 format = require('./utils/format');
6
7
8/**
9 * Expose `Query`
10 */
11
12module.exports = exports = Query;
13
14/**
15 * Create a new `Query`
16 * @constructor
17 *
18 * @return {Query}
19 * @api private
20 */
21
22function Query(){
23 this.parameters = [];
24}
25
26/**
27 * Set a new parameter
28 * Since all possibilities provided by Solr are not available in the `Query` object, `set()` is there to fit this gap.
29 *
30 * @param {String} parameter - string, special characters have to be correctly encoded or the request will fail.
31 *
32 * @return {Query} - allow chaining
33 * @api public
34 */
35Query.prototype.set = function(parameter){
36 var self = this;
37 this.parameters.push(parameter);
38 return self;
39}
40
41/**
42 * Set the query parser to use with this request.
43 *
44 * @param {String} type - name of the query parser e.g: 'dismax'
45 *
46 * @return {Query}
47 * @api public
48 */
49
50Query.prototype.defType = function(type){
51 var self = this;
52 var parameter = 'defType=' + type;
53 this.parameters.push(parameter);
54 return self;
55}
56
57/**
58 * Set the Request Handler used to process the request based on its `name`.
59 * Works only if no Request Handler has been configured with `/select` as its name in solrconfig.xml.
60 *
61 * @param {String} name - name of the Request Handler
62 *
63 * @return {Query}
64 * @api public
65 */
66
67Query.prototype.requestHandler =
68Query.prototype.qt = function(name){
69 var self = this;
70 var parameter = 'qt=' + name;
71 this.parameters.push(parameter);
72 return self;
73}
74
75/**
76 * Set the main query
77 *
78 * @param {String|Object} q -
79 *
80 * @return {Query}
81 * @api public
82 */
83
84Query.prototype.q = function(q){
85 var self = this;
86 var parameter ='q=';
87 if ( typeof(q) === 'string' ){
88 parameter += encodeURIComponent(q);
89 }else{
90 parameter += querystring.stringify(q, '%20AND%20',':');
91 }
92 this.parameters.push(parameter);
93 return self;
94}
95
96/**
97 * Set the offset where the set of returned documents should begin.
98 *
99 * @param {Number} start - the offset where the set of returned documents should begin.
100 *
101 * @return {Query}
102 * @api public
103 */
104
105Query.prototype.start = function(start){
106 var self = this;
107 var parameter = 'start=' + start ;
108 this.parameters.push(parameter);
109 return self;
110}
111
112/**
113 * Set the maximum number of documents returned
114 *
115 * @param {Number} rows - number of documents
116 *
117 * @return {Query}
118 * @api public
119 */
120Query.prototype.rows = function(rows){
121 var self = this;
122 var parameter = 'rows=' + rows ;
123 this.parameters.push(parameter);
124 return self;
125}
126
127/**
128 * Sort a result in descending or ascending order based on one or more fields.
129 *
130 * @param {Object} options -
131 *
132 * @return {Query}
133 * @api public
134 */
135
136Query.prototype.sort = function(options){
137 var self = this;
138 var parameter = 'sort=';
139 parameter += querystring.stringify(options, ',' , '%20');
140 this.parameters.push(parameter);
141 return self;
142}
143
144/**
145 * Filter the set of documents found before to return the result with the given range determined by `field`, `start` and `end`.
146 *
147 * @param {Array|Object} options -
148 * @param {String} options.field - the name of the field where the range is applied
149 * @param {String|Number|Date} options.start - the offset where the range starts
150 * @param {String|Number|Date} options.end - the offset where the range ends
151 *
152 * @return {Query}
153 * @api public
154 *
155 * @example
156 * var query = client.createQuery();
157 * query.q({ '*' : '*' }).rangeFilter({ field : 'id', start : 100, end : 200})
158 * // also works
159 * query.q({ '*' : '*' }).rangeFilter([{ field : 'id', start : 100, end : 200},{ field : 'date', start : new Date(), end : new Date() - 3600}]);
160 */
161
162Query.prototype.rangeFilter = function(options){
163 var self = this;
164 options = format.dateISOify(options);
165 var parameter = 'fq=';
166 if(Array.isArray(options)){
167 parameter += "(";
168 var filters = options.map(function(option){
169 var key = option.field;
170 var filter = {};
171 filter[key] = '[' + encodeURIComponent(option.start) + '%20TO%20' + encodeURIComponent(option.end) + ']';
172 return format.stringify(filter, '',':');
173 });
174 parameter += filters.join('%20AND%20');
175 parameter += ")";
176 }else{
177 var key = options.field;
178 var filter = {};
179 filter[key] = '[' + encodeURIComponent(options.start) + '%20TO%20' + encodeURIComponent(options.end) + ']';
180 parameter += format.stringify(filter, '',':');
181 }
182 this.parameters.push(parameter);
183 return self;
184}
185
186/**
187 * Filter the set of documents found before to return the result with the given `field` and `value`.
188 *
189 * @param {String} field - name of field
190 * @param {String|Number|Date} value - value of the field that must match
191 *
192 * @return {Query}
193 * @api public
194 *
195 * @example
196 * var query = client.createQuery();
197 * query.q({ '*' : '*' }).matchFilter('id', 100)
198 */
199
200Query.prototype.matchFilter = function(field,value){
201 var self = this;
202 value = format.dateISOify(value);
203 var parameter = 'fq=';
204 parameter += field + ':' + encodeURIComponent(value);
205 this.parameters.push(parameter);
206 return self;
207}
208
209/**
210 * Specify a set of fields to return.
211 *
212 * @param {String|Array} field - field name
213 *
214 * @return {Query}
215 * @api public
216 */
217
218Query.prototype.fl =
219Query.prototype.restrict = function(fields){
220 var self = this;
221 var parameter = 'fl=';
222 if(typeof(fields) === 'string'){
223 parameter += fields;
224 }else{
225 parameter += fields.join(',');
226 }
227 this.parameters.push(parameter);
228 return self;
229}
230
231/**
232 * Set the time allowed for a search to finish.
233 * Partial results may be returned (if there are any).
234 *
235 * @param {String|Number} time - time is in milliseconds. Values <= 0 mean no time restriction.
236 *
237 * @return {Query}
238 * @api public
239 */
240
241Query.prototype.timeout = function(time){
242 var self = this;
243 var parameter = 'timeAllowed=' + time;
244 this.parameters.push(parameter);
245 return self;
246}
247
248/**
249 * Group documents with the given `field`
250 *
251 * @param {String} field - field name
252 *
253 * @return {Query}
254 * @api public
255 */
256
257Query.prototype.groupBy = function(field){
258 var self = this;
259 this.group({
260 'field': field
261 });
262 return self;
263}
264
265/**
266 * Group documents using field collapsing or result grouping feature.
267 * Field Collapsing collapses a group of results with the same field value down to a single (or fixed number) of entries.
268 * Result Grouping groups documents with a common field value into groups, returning the top documents per group, and the top groups based on what documents are in the groups.
269 *
270 * @param {Object} options
271 * @param {Boolean} [options.on=true] - if false, turn off result grouping, otherwise turn on.
272 * @param {String} options.field - Group based on the unique values of a field.
273 * @param {Number} [options.limit=1] - The number of results (documents) to return for each group. Solr's default value is 1.
274 * @param {Number} options.offset - The offset into the document list of each group.
275 * @param {String} [options.sort="score desc"] - How to sort documents within a single group. Defaults to the same value as the sort parameter.
276 * @param {String} options.format - if simple, the grouped documents are presented in a single flat list. The start and rows parameters refer to numbers of documents instead of numbers of groups.
277 * @param {Boolean} options.main - If true, the result of the last field grouping command is used as the main result list in the response, using group.format=simple.
278 * @param {Boolean} [options.ngroups=false] - If true, includes the number of groups that have matched the query. Default is false.
279 * @param {Boolean} options.truncate - If true, facet counts are based on the most relevant document of each group matching the query. Same applies for StatsComponent. Default is false.
280 * @param {Number} [options.cache=0] - If > 0 enables grouping cache. Grouping is executed actual two searches. This option caches the second search. A value of 0 disables grouping caching. Default is 0.
281 *
282 * @return {Query}
283 * @api public
284 */
285
286Query.prototype.group = function(options){
287 var self = this;
288 if(options.on === false){
289 this.parameters.push('group=false');
290 }else{
291 this.parameters.push('group=true');
292 }
293 if( options.field ){
294 this.parameters.push('group.field=' + options.field);
295 }
296 if( options.limit !== undefined){
297 this.parameters.push('group.limit=' + options.limit);
298 }
299 if( options.offset !== undefined){
300 this.parameters.push('group.offset=' + options.offset);
301 }
302 if( options.sort ){
303 this.parameters.push('group.sort=' + encodeURIComponent(options.sort));
304 }
305 if( options.format ){
306 this.parameters.push('group.format=' + encodeURIComponent(options.format));
307 }
308 if( options.main !== undefined){
309 this.parameters.push('group.main=' + options.main);
310 }
311 if( options.ngroups !== undefined){
312 this.parameters.push('group.ngroups=' + options.ngroups);
313 }
314 if( options.truncate !== undefined){
315 this.parameters.push('group.truncate=' + options.truncate);
316 }
317 if( options.cache !== undefined){
318 this.parameters.push('group.cache.percent=' + options.cache);
319 }
320 return self;
321}
322
323/**
324 * Create a facet
325 *
326 * @param {Object} options - set of options to create a facet
327 * @param {Boolean} [options.on=true] - Turn on or off facet
328 * @param {String} [options.query] - This parameter allows you to specify an arbitrary query in the Lucene default syntax to generate a facet count. By default, faceting returns a count of the unique terms for a "field", while facet.query allows you to determine counts for arbitrary terms or expressions.
329 * @param {String} options.field - This parameter allows you to specify a field which should be treated as a facet. It will iterate over each Term in the field and generate a facet count using that Term as the constraint.
330 * @param {String} [options.prefix] - Limits the terms on which to facet to those starting with the given string prefix.
331 * @param {String} [options.sort] - This param determines the ordering of the facet field constraints.count
332 * @param {Number} [options.limit=100] - This parameter indicates the maximum number of constraint counts that should be returned for the facet fields. A negative value means unlimited.The solr's default value is 100.
333 * @param {Number} [options.offset=0] - This param indicates an offset into the list of constraints to allow paging.The solr's default value is 0.
334 * @param {Number} [options.mincount=0] - This parameter indicates the minimum counts for facet fields should be included in the response. The solr's default value is 0.
335 * @param {Boolean} [options.missing=false] - Set to `true` this param indicates that in addition to the Term based constraints of a facet field, a count of all matching results which have no value for the field should be computed. The solr's default value is false.
336 * @param {String} [options.method="fc"] - This parameter indicates what type of algorithm/method to use when faceting a field.The solr's default value is fc (except for BoolField).
337 *
338 * @return {Query}
339 * @api public
340 */
341Query.prototype.facet = function(options){
342 var self = this;
343 if(options.on === false){
344 this.parameters.push('facet=false');
345 }else{
346 this.parameters.push('facet=true');
347 }
348 if(options.query){
349 this.parameters.push('facet.query=' + encodeURIComponent(options.query))
350 }
351 if(options.field){
352 this.parameters.push('facet.field=' + options.field)
353 }
354 if(options.prefix){
355 this.parameters.push('facet.prefix=' + encodeURIComponent(options.prefix))
356 }
357 if(options.sort){
358 this.parameters.push('facet.sort=' + encodeURIComponent(options.sort))
359 }
360 if(options.limit !== undefined){
361 this.parameters.push('facet.limit=' + options.limit);
362 }
363 if(options.offset !== undefined){
364 this.parameters.push('facet.offset=' + options.offset);
365 }
366 if(options.mincount !== undefined){
367 this.parameters.push('facet.mincount=' + options.mincount);
368 }
369 if(options.missing !== undefined){
370 this.parameters.push('facet.missing=' + options.missing);
371 }
372 if(options.method){
373 this.parameters.push('facet.method=' + options.method);
374 }
375 return self;
376}
377
378/**
379 * Create a MoreLikeThis. MoreLikeThis constructs a lucene query based on terms within a document.
380 *
381 * @param {Object} options - set of options to create a morelikethis
382 * @param {Boolean} [options.on=true] - Turn on or off morelikethis
383 * @param {String|Array} [options.fl] - The fields to use for similarity. NOTE: if possible, these should have a stored TermVector
384 * @param {Number} [options.count] - The number of similar documents to return for each result.
385 * @param {Number} [options.mintf] - Minimum Term Frequency - the frequency below which terms will be ignored in the source doc.
386 * @param {Number} [options.mindf] - Minimum Document Frequency - the frequency at which words will be ignored which do not occur in at least this many docs.
387 * @param {Number} [options.minwl] - minimum word length below which words will be ignored.
388 * @param {Number} [options.maxwl] - maximum word length above which words will be ignored.
389 * @param {Number} [options.maxqt] - maximum number of query terms that will be included in any generated query.
390 * @param {Number} [options.maxntp] - maximum number of tokens to parse in each example doc field that is not stored with TermVector support.
391 * @param {Boolean} [options.boost] - set if the query will be boosted by the interesting term relevance.
392 * @param {String|Object} [options.qf] - Query fields and their boosts using the same format as that used in DisMaxQParserPlugin. These fields must also be specified in mlt.fl.
393 *
394 * @return {Query}
395 * @api public
396 */
397
398Query.prototype.mlt = function(options){
399 var self = this;
400 if(options.on === false){
401 this.parameters.push('mlt=false');
402 }else{
403 this.parameters.push('mlt=true');
404 }
405 if(options.fl){
406 if(options.fl instanceof Array) options.fl = options.fl.join(',');
407 this.parameters.push('mlt.fl=' + encodeURIComponent(options.fl))
408 }
409 if(options.count !== undefined){
410 this.parameters.push('mlt.count=' + options.count)
411 }
412 if(options.mintf !== undefined){
413 this.parameters.push('mlt.mintf=' + options.mintf)
414 }
415 if(options.mindf !== undefined){
416 this.parameters.push('mlt.mindf=' + options.mindf);
417 }
418 if(options.minwl !== undefined){
419 this.parameters.push('mlt.minwl=' + options.minwl)
420 }
421 if(options.maxwl !== undefined ){
422 this.parameters.push('mlt.maxwl=' + options.maxwl)
423 }
424 if(options.maxqt !== undefined){
425 this.parameters.push('mlt.maxqt=' + options.maxqt)
426 }
427 if(options.maxntp !== undefined){
428 this.parameters.push('mlt.maxntp=' + options.maxntp);
429 }
430 if(options.boost !== undefined){
431 this.parameters.push('mlt.boost=' + options.boost);
432 }
433 if(options.qf){
434 if( typeof options.qf === 'object'){
435 var parameter = querystring.stringify(options.qf, '%20' , '^');;
436 }else{
437 var parameter = encodeURIComponent(options.qf);
438 }
439 this.parameters.push('mlt.qf=' + parameter);
440 }
441 return self;
442}
443
444/**
445 * DisMax parameters
446 * do not forget to use `.dismax()` when using these parameters
447 */
448
449/**
450 * Use the DisMax query parser
451 *
452 * @return {Query}
453 * @api public
454 */
455
456Query.prototype.dismax = function(){
457 var self = this;
458 this.defType('dismax');
459 return self;
460}
461
462/**
463 * Set the "boosts" to associate with each fields
464 *
465 * @param {Object} options -
466 *
467 * @return {Query}
468 * @api public
469 *
470 * @example
471 * var query = client.createQuery();
472 * query.qf({title : 2.2, description : 0.5 });
473 */
474
475Query.prototype.qf = function(options){
476 var self = this;
477 var parameter = 'qf=' ;
478 parameter += querystring.stringify(options, '%20' , '^');
479 this.parameters.push(parameter);
480 return self;
481}
482
483/**
484 * Set the minimum number or percent of clauses that must match.
485 *
486 * @param {String|Number} minimum - number or percent of clauses that must match
487 *
488 * @return {Query}
489 * @api public
490 *
491 * @example
492 * var query = client.createQuery();
493 * query.mm(2); // or query.mm('75%');
494 */
495
496Query.prototype.mm = function(minimum){
497 var self = this;
498 var parameter = 'mm=' + minimum;
499 this.parameters.push(parameter);
500 return self;
501}
502
503/**
504 * Set the Phrase Fields parameter.
505 * Once the list of matching documents has been identified using the "fq" and "qf" params, the "pf" param can be used to "boost" the score of documents in cases where all of the terms
506 * in the "q" param appear in close proximity.
507 *
508 * @param {Object} options -
509 *
510 * @return {Query}
511 * @api public
512 */
513
514Query.prototype.pf = function(options){
515 var self = this;
516 var parameter = 'pf=' ;
517 parameter += querystring.stringify(options, '%20' , '^');
518 this.parameters.push(parameter);
519 return self;
520}
521
522/**
523 * Set the phrase slop allowed in a query.
524 *
525 * @param {Number} slop - Amount of phrase slop allowed by the query filter. This value should represent the maximum number of words allowed between words in a field that match a phrase in the query.
526 *
527 * @return {Query}
528 * @api public
529 */
530
531Query.prototype.ps = function(slop){
532 var self = this;
533 var parameter = 'ps=' + slop;
534 this.parameters.push(parameter);
535 return self;
536};
537
538/**
539 * Set the query slop allowed in a query.
540 *
541 * @param {Number} slop - Amount of query slop allowed by the query filter. This value should be used to affect boosting of query strings.
542 *
543 * @return {Query}
544 * @api public
545 */
546Query.prototype.qs = function(slop){
547 var self = this;
548 var parameter = 'qs=' + slop;
549 this.parameters.push(parameter);
550 return self;
551};
552
553/**
554 * Set the tiebreaker in DisjunctionMaxQueries (should be something much less than 1)
555 *
556 * @param {Float|Number} tiebreaker -
557 *
558 * @return {Query}
559 * @api public
560 */
561
562Query.prototype.tie = function(tiebreaker){
563 var self = this;
564 var parameter = 'tie=' + tiebreaker;
565 this.parameters.push(parameter);
566 return self;
567}
568
569/**
570 * Set the Boost Query parameter.
571 * A raw query string (in the SolrQuerySyntax) that will be included with the user's query to influence the score. If this is a BooleanQuery with a default boost (1.0f) then the individual clauses will be added directly to the main query. Otherwise, the query will be included as is.
572 *
573 * @param {Object} options -
574 *
575 * @return {Query}
576 * @api public
577 */
578
579Query.prototype.bq = function(options){
580 var self = this;
581 var parameter = 'bq=' ;
582 parameter += querystring.stringify(options, '%20' , '^');
583 this.parameters.push(parameter);
584 return self;
585}
586
587
588/**
589 * Set the Functions (with optional boosts) that will be included in the user's query to influence the score.
590 * @param {String} functions - e.g.: `recip(rord(myfield),1,2,3)^1.5`
591 *
592 * @return {Query}
593 * @api public
594 */
595
596Query.prototype.bf = function(functions){
597 var self = this;
598 var parameter = 'bf=' + functions;
599 this.parameters.push(parameter);
600 return self;
601}
602
603/**
604 * Build a querystring with the array of `this.parameters`.
605 *
606 * @return {String}
607 * @api private
608 */
609Query.prototype.build = function(){
610 return this.parameters.join('&');
611}
612