File Coverage

blib/lib/ElasticSearch/SearchBuilder.pm
Criterion Covered Total %
statement 592 616 96.1
branch 210 240 87.5
condition 68 90 75.5
subroutine 182 186 97.8
pod 3 3 100.0
total 1055 1135 92.9


line stmt bran cond sub pod time code
1             package ElasticSearch::SearchBuilder;
2              
3 10     10   870746 use Carp;
  10         26  
  10         1039  
4 10     10   60 use strict;
  10         21  
  10         2226  
5 10     10   58 use warnings;
  10         25  
  10         322  
6 10     10   56 use Scalar::Util ();
  10         27  
  10         188052  
7              
8             our $VERSION = '0.19';
9              
10             my %SPECIAL_OPS = (
11             query => {
12             '=' => [ 'match', 0 ],
13             '==' => [ 'match_phrase', 0 ],
14             '!=' => [ 'match', 1 ],
15             '<>' => [ 'match', 1 ],
16             '>' => [ 'range', 0 ],
17             '>=' => [ 'range', 0 ],
18             '<' => [ 'range', 0 ],
19             '<=' => [ 'range', 0 ],
20             'gt' => [ 'range', 0 ],
21             'lt' => [ 'range', 0 ],
22             'gte' => [ 'range', 0 ],
23             'lte' => [ 'range', 0 ],
24             '^' => [ 'match_phrase_prefix', 0 ],
25             '*' => [ 'wildcard', 0 ],
26             },
27             filter => {
28             '=' => [ 'terms', 0 ],
29             '!=' => [ 'terms', 1 ],
30             '<>' => [ 'terms', 1 ],
31             '>' => [ 'range', 0 ],
32             '>=' => [ 'range', 0 ],
33             '<' => [ 'range', 0 ],
34             '<=' => [ 'range', 0 ],
35             'gt' => [ 'range', 0 ],
36             'lt' => [ 'range', 0 ],
37             'gte' => [ 'range', 0 ],
38             'lte' => [ 'range', 0 ],
39             '^' => [ 'prefix', 0 ],
40             'exists' => [ 'exists', 0 ],
41             'not_exists' => [ 'exists', 0 ],
42             'missing' => [ 'missing', 0 ],
43             'not_missing' => [ 'missing', 1 ],
44             }
45             );
46              
47             my %RANGE_MAP = (
48             '>' => 'gt',
49             '<' => 'lt',
50             '>=' => 'gte',
51             '<=' => 'lte'
52             );
53              
54             #===================================
55             sub new {
56             #===================================
57 10     10 1 778 my $proto = shift;
58 10   66     81 my $class = ref($proto) || $proto;
59 10         51 return bless {}, $class;
60             }
61              
62             #===================================
63 527     527 1 3141521 sub query { shift->_top_recurse( 'query', @_ ) }
64 307     307 1 1989870 sub filter { shift->_top_recurse( 'filter', @_ ) }
65             #===================================
66              
67             #======================================================================
68             # top-level
69             #======================================================================
70              
71             #===================================
72             sub _top_ARRAYREF {
73             #===================================
74 186     186   380 my ( $self, $type, $params, $logic ) = @_;
75 186   100     651 $logic ||= 'or';
76              
77 186         529 my @args = @$params;
78 186         243 my @clauses;
79              
80 186         431 while (@args) {
81 349         585 my $el = shift @args;
82             my $clause = $self->_SWITCH_refkind(
83             'ARRAYREFs',
84             $el,
85             { ARRAYREF => sub {
86 12 100   12   59 $self->_recurse( $type, $el ) if @$el;
87             },
88             HASHREF => sub {
89 107 100   107   453 $self->_recurse( $type, $el, 'and' ) if %$el;
90             },
91 2     2   5 HASHREFREF => sub {$$el},
92             SCALAR => sub {
93 226     226   858 $self->_recurse( $type, { $el => shift(@args) } );
94             },
95 2     2   27 UNDEF => sub { croak "UNDEF in arrayref not supported" },
96             }
97 349         5954 );
98 341 100       4571 push @clauses, $clause if $clause;
99             }
100 178         479 return $self->_join_clauses( $type, $logic, \@clauses );
101             }
102              
103             #===================================
104             sub _top_HASHREF {
105             #===================================
106 1257     1257   2402 my ( $self, $type, $params ) = @_;
107              
108 1257         1645 my ( @clauses, $filter );
109 1257         5271 $params = {%$params};
110 1257         4414 for my $k ( sort keys %$params ) {
111 1292         2339 my $v = $params->{$k};
112              
113 1292         1603 my $clause;
114              
115             # ($k => $v) is either a special unary op or a regular hashpair
116 1292 100       17910 if ( $k =~ /^-./ ) {
117 319         859 my $op = substr $k, 1;
118 319         1009 my $not = $op =~ s/^not_//;
119 319 100 100     1094 croak "Invalid op 'not_$op'"
      66        
120             if $not and $op eq 'cache' || $op eq 'nocache';
121              
122 317 100 66     1008 if ( $op eq 'filter' and $type eq 'query' ) {
123 7         25 $filter = $self->_recurse( 'filter', $v );
124 7 100       30 $filter = $self->_negate_clause( 'filter', $filter )
125             if $not;
126 7         21 next;
127             }
128              
129 310 50       2011 my $handler = $self->can("_${type}_unary_$op")
130             or croak "Unknown $type op '$op'";
131              
132 310         1020 $clause = $handler->( $self, $v );
133 255 100       15275 $clause = $self->_negate_clause( $type, $clause )
134             if $not;
135             }
136             else {
137 973         2343 my $method = $self->_METHOD_FOR_refkind( "_hashpair", $v );
138 973         13194 $clause = $self->$method( $type, $k, $v );
139             }
140 949 100       4369 push @clauses, $clause if $clause;
141             }
142              
143 921         2924 my $clause = $self->_join_clauses( $type, 'and', \@clauses );
144              
145 921 100       4882 return $clause unless $filter;
146 7 100       56 return $clause
147             ? { filtered => { query => $clause, filter => $filter } }
148             : { constant_score => { filter => $filter } };
149             }
150              
151             #===================================
152             sub _top_SCALAR {
153             #===================================
154 3     3   10 my ( $self, $type, $params ) = @_;
155 3 100       24 return $type eq 'query'
156             ? { match => { _all => $params } }
157             : { term => { _all => $params } };
158             }
159              
160             #===================================
161 4     4   6 sub _top_HASHREFREF { return ${ $_[2] } }
  4         11  
162 2     2   5 sub _top_SCALARREF { return ${ $_[2] } }
  2         9  
163 4     4   8 sub _top_UNDEF {return}
164             #===================================
165              
166             #======================================================================
167             # HASH PAIRS
168             #======================================================================
169              
170             #===================================
171             sub _hashpair_ARRAYREF {
172             #===================================
173 60     60   157 my ( $self, $type, $k, $v ) = @_;
174              
175 60 100       297 my @v = @$v ? @$v : [undef];
176              
177             # put apart first element if it is an operator (-and, -or)
178 60 100 66     483 my $op
179             = $v[0] && ( $v[0] eq '-and' || $v[0] eq '-or' )
180             ? shift @v
181             : '';
182 60 100       191 my $logic = $op ? substr( $op, 1 ) : '';
183 60         142 my @distributed = map { +{ $k => $_ } } @v;
  130         393  
184              
185             # if all values are defined scalars then try to use
186             # a terms query/filter
187              
188 60 100 100     303 if ( $logic ne 'and' and $type eq 'filter' ) {
189 14         27 my $scalars = 0;
190 14         31 for (@v) {
191 30 100 100     155 $scalars++ if defined and !ref;
192             }
193 14 100       62 return $self->_filter_field_terms( $k, 'terms', \@v )
194             if $scalars == @v;
195              
196             }
197 55 100       173 unshift @distributed, $op
198             if $op;
199              
200 55         718 return $self->_recurse( $type, \@distributed, $logic );
201             }
202              
203             #===================================
204             sub _hashpair_HASHREF {
205             #===================================
206 602     602   1250 my ( $self, $type, $k, $v, $logic ) = @_;
207 602   50     2420 $logic ||= 'and';
208              
209 602         786 my @clauses;
210              
211 602         2145 for my $orig_op ( sort keys %$v ) {
212 622         816 my $clause;
213              
214 622         1085 my $val = $v->{$orig_op};
215 622         1211 my $op = $orig_op;
216 622         1215 $op =~ s/^-//;
217              
218 622 100       2395 if ( my $hash_op = $SPECIAL_OPS{$type}{$op} ) {
219 329         681 my ( $handler, $not ) = @$hash_op;
220 329         722 $handler = "_${type}_field_$handler";
221 329         1387 $clause = $self->$handler( $k, $op, $val );
222 204 100       2235 $clause = $self->_negate_clause( $type, $clause )
223             if $not;
224             }
225             else {
226 293         1007 my $not = ( $op =~ s/^not_// );
227 293         771 my $handler = "_${type}_field_$op";
228 293 100       1267 croak "Unknown $type operator '$op'"
229             unless $self->can($handler);
230              
231 291         846 $clause = $self->$handler( $k, $op, $val );
232 166 100       2323 $clause = $self->_negate_clause( $type, $clause )
233             if $not;
234             }
235 370         1374 push @clauses, $clause;
236             }
237              
238 350         1241 return $self->_join_clauses( $type, $logic, \@clauses );
239             }
240              
241             #===================================
242             sub _hashpair_SCALARREF {
243             #===================================
244 2     2   5 my ( $self, $type, $k, $v ) = @_;
245 2         8 return { $k => $$v };
246             }
247              
248             #===================================
249             sub _hashpair_SCALAR {
250             #===================================
251 343     343   632 my ( $self, $type, $k, $v ) = @_;
252 343 100       2123 return $type eq 'query'
253             ? { match => { $k => $v } }
254             : { term => { $k => $v } };
255             }
256              
257             #===================================
258             sub _hashpair_UNDEF {
259             #===================================
260 40     40   112 my ( $self, $type, $k, $v ) = @_;
261 40 100       520 return { missing => { field => $k } }
262             if $type eq 'filter';
263 6         116 croak "$k => UNDEF not a supported query op";
264             }
265              
266             #======================================================================
267             # CLAUSE UTILS
268             #======================================================================
269              
270             #===================================
271             sub _negate_clause {
272             #===================================
273 169     169   391 my ( $self, $type, $clause ) = @_;
274 169 100       1069 return $type eq 'filter'
275             ? { not => { filter => $clause } }
276             : $self->_merge_bool_queries( 'must_not', [$clause] );
277             }
278              
279             #===================================
280             sub _join_clauses {
281             #===================================
282 1560     1560   2943 my ( $self, $type, $logic, $clauses ) = @_;
283              
284 1560 100       3499 return if @$clauses == 0;
285 1537 100       5481 return $clauses->[0] if @$clauses == 1;
286              
287 214 100       592 if ( $logic eq 'and' ) {
288 89         290 $clauses = $self->_merge_range_clauses($clauses);
289 73 100       222 return $clauses->[0] if @$clauses == 1;
290             }
291 195 100       918 if ( $type eq 'query' ) {
292 104 100       237 my $op = $logic eq 'and' ? 'must' : 'should';
293 104         328 return $self->_merge_bool_queries( $op, $clauses );
294              
295             }
296 91         579 return { $logic => $clauses };
297             }
298              
299             #===================================
300             sub _merge_bool_queries {
301             #===================================
302 206     206   302 my $self = shift;
303 206         297 my $op = shift;
304 206         264 my $queries = shift;
305 206         249 my %bool;
306              
307 206         434 for my $query (@$queries) {
308 336         818 my ( $type, $clauses ) = %$query;
309 336 100       873 if ( $type eq 'bool' ) {
310 59         252 $clauses = {%$clauses};
311 177 50       413 my ( $must, $should, $not )
312 59         154 = map { ref $_ eq 'HASH' ? [$_] : $_ }
313 59         105 delete @{$clauses}{qw(must should must_not)};
314 59 50       200 if ( !keys %$clauses ) {
315 59 100       182 if ( $op eq 'must' ) {
    100          
316 21 100       52 push @{ $bool{must} }, @$must if $must;
  5         16  
317 21 100       48 push @{ $bool{must_not} }, @$not if $not;
  3         9  
318 21 100       56 next unless $should;
319 14 50       81 if ( @$should == 1 ) {
    50          
320 0         0 push @{ $bool{must} }, $should->[0];
  0         0  
321 0         0 next;
322             }
323             elsif ( @$queries == 1 ) {
324 0         0 push @{ $bool{should} }, @$should;
  0         0  
325 0         0 next;
326             }
327 14         21 delete @{ $query->{bool} }{ 'must', 'must_not' };
  14         35  
328             }
329             elsif ( $op eq 'should' ) {
330 16 50       50 unless ($not) {
331 16 100 33     64 if ($should) {
    50          
332 8 50       21 if ( !$must ) {
333 8         9 push @{ $bool{should} }, @$should;
  8         32  
334 8         24 next;
335             }
336             }
337             elsif ( $must and @$must == 1 ) {
338 0         0 push @{ $bool{should} }, $must->[0];
  0         0  
339 0         0 next;
340             }
341             }
342             }
343             else {
344 22 100       53 if ($must) {
345 5 0 33     28 if ( @$must == 1 and !$should and !$not ) {
      33        
346 0         0 push @{ $bool{must_not} }, @$must;
  0         0  
347 0         0 next;
348             }
349             }
350             else {
351 17 100       59 if ($should) {
    50          
352 15 50       47 if ( !$not ) {
353 15         22 push @{ $bool{must_not} }, @$should;
  15         47  
354 15         59 next;
355             }
356             }
357             elsif ($not) {
358 2         5 push @{ $bool{must} }, @$not;
  2         4  
359 2         6 next;
360             }
361             }
362             }
363             }
364             }
365 304         309 push @{ $bool{$op} }, $query;
  304         1164  
366             }
367 206 100       721 if ( keys %bool == 1 ) {
368 203         395 my ( $k, $v ) = %bool;
369 203 100 100     951 return $v->[0]
370             if $k ne 'must_not' and @$v == 1;
371             }
372              
373 204         1109 return { bool => \%bool };
374             }
375              
376             my %Range_Clauses = (
377             range => 1,
378             numeric_range => 1,
379             );
380              
381             #===================================
382             sub _merge_range_clauses {
383             #===================================
384 89     89   144 my $self = shift;
385 89         125 my $clauses = shift;
386 89         112 my ( @new, %merge );
387              
388 89         187 for (@$clauses) {
389 207         463 my ($type) = keys %$_;
390              
391 207 100 100     792 if ( $Range_Clauses{$type} and not exists $_->{$type}{_cache} ) {
392 53         65 my ( $field, $constraint ) = %{ $_->{$type} };
  53         142  
393              
394 53         134 for my $op ( keys %$constraint ) {
395 53 100       188 if ( defined $merge{$type}{$field}{$op} ) {
396 16         268 croak "Duplicate '$type:$op' exists "
397             . "for field '$field', with values: "
398             . $merge{$type}{$field}{$op} . ' and '
399             . $constraint->{$op};
400             }
401              
402 37         164 $merge{$type}{$field}{$op} = $constraint->{$op};
403             }
404             }
405 154         369 else { push @new, $_ }
406             }
407              
408 73         227 for my $type ( keys %merge ) {
409 8         11 for my $field ( keys %{ $merge{$type} } ) {
  8         23  
410 8         70 push @new, { $type => { $field => $merge{$type}{$field} } };
411             }
412             }
413 73         249 return \@new;
414             }
415              
416             #======================================================================
417             # UNARY OPS
418             #======================================================================
419             # Shared query/filter unary ops
420             #======================================================================
421              
422             #===================================
423 5     5   26 sub _query_unary_all { shift->_unary_all( 'query', shift ) }
424 21     21   98 sub _query_unary_or { shift->_unary_and( 'query', shift, 'or' ) }
425 36     36   146 sub _query_unary_and { shift->_unary_and( 'query', shift, 'and' ) }
426 17     17   67 sub _query_unary_not { shift->_unary_not( 'query', shift, ) }
427 9     9   36 sub _query_unary_ids { shift->_unary_ids( 'query', shift ) }
428 3     3   206 sub _query_unary_has_child { shift->_unary_child( 'query', shift ) }
429 3     3   15 sub _query_unary_has_parent { shift->_unary_parent( 'query', shift ) }
430             #===================================
431              
432             #===================================
433 5     5   18 sub _filter_unary_all { shift->_unary_all( 'filter', shift ) }
434 21     21   73 sub _filter_unary_or { shift->_unary_and( 'filter', shift, 'or' ) }
435 37     37   159 sub _filter_unary_and { shift->_unary_and( 'filter', shift, 'and' ) }
436 16     16   88 sub _filter_unary_not { shift->_unary_not( 'filter', shift, ) }
437 9     9   47 sub _filter_unary_ids { shift->_unary_ids( 'filter', shift ) }
438 3     3   16 sub _filter_unary_has_child { shift->_unary_child( 'filter', shift ) }
439 3     3   14 sub _filter_unary_has_parent { shift->_unary_parent( 'filter', shift ) }
440             #===================================
441              
442             #===================================
443             sub _unary_all {
444             #===================================
445 10     10   22 my ( $self, $type, $v ) = @_;
446 10 100 100     60 $v = {} unless $v and ref $v eq 'HASH';
447             $self->_SWITCH_refkind(
448             "Unary -all",
449             $v,
450             { HASHREF => sub {
451 10 100   10   61 my $p = $self->_hash_params( 'all', $v, [],
452             $type eq 'query' ? [ 'boost', 'norms_field' ] : [] );
453 9         41 return { match_all => $p };
454             },
455             }
456 10         89 );
457             }
458              
459             #===================================
460             sub _unary_and {
461             #===================================
462 115     115   331 my ( $self, $type, $v, $op ) = @_;
463             $self->_SWITCH_refkind(
464             "Unary -$op",
465             $v,
466 59     59   178 { ARRAYREF => sub { return $self->_top_ARRAYREF( $type, $v, $op ) },
467             HASHREF => sub {
468 24         99 return $op eq 'or'
469             ? $self->_top_ARRAYREF( $type,
470 48 100   48   246 [ map { $_ => $v->{$_} } ( sort keys %$v ) ], $op )
471             : $self->_top_HASHREF( $type, $v );
472             },
473              
474             SCALAR => sub {
475 4     4   105 croak "$type -$op => '$v' makes little sense, "
476             . "use filter -exists => '$v' instead";
477             },
478 4     4   44 UNDEF => sub { croak "$type -$op => undef not supported" },
479             }
480 115         1883 );
481             }
482              
483             #===================================
484             sub _unary_not {
485             #===================================
486 33     33   77 my ( $self, $type, $v ) = @_;
487             my $clause = $self->_SWITCH_refkind(
488             "Unary -not",
489             $v,
490 14     14   49 { ARRAYREF => sub { $self->_top_ARRAYREF( $type, $v ) },
491 15     15   42 HASHREF => sub { $self->_top_HASHREF( $type, $v ) },
492             SCALAR => sub {
493 2     2   33 croak "$type -not => '$v' makes little sense, "
494             . "use filter -missing => '$v' instead";
495             },
496 2     2   27 UNDEF => sub { croak "$type -not => undef not supported" },
497             }
498 33 100       520 ) or return;
499              
500 26         279 return $self->_negate_clause( $type, $clause );
501             }
502              
503             #===================================
504             sub _unary_ids {
505             #===================================
506 18     18   40 my ( $self, $type, $v ) = @_;
507             return $self->_SWITCH_refkind(
508             "Unary -ids",
509             $v,
510 4     4   23 { SCALAR => sub { return { ids => { values => [$v] } } },
511             ARRAYREF => sub {
512 6 100   6   53 return unless @$v;
513 4         27 return { ids => { values => $v } };
514             },
515             HASHREF => sub {
516 8 100   8   67 my $p = $self->_hash_params( 'ids', $v, ['values'],
517             $type eq 'query' ? [ 'type', 'boost' ] : ['type'] );
518 8 100       41 $p->{values} = [ $p->{values} ] unless ref $p->{values};
519 8         34 return { ids => $p };
520             },
521             }
522 18         249 );
523             }
524              
525             #===================================
526             sub _query_unary_top_children {
527             #===================================
528 3     3   7 my ( $self, $v ) = @_;
529             return $self->_SWITCH_refkind(
530             "Unary query -top_children",
531             $v,
532             { HASHREF => sub {
533 2     2   15 my $p = $self->_hash_params(
534             'top_children', $v,
535             [ 'query', 'type' ],
536             [qw(_scope score factor incremental_factor)]
537             );
538 2         11 $p->{query} = $self->_recurse( 'query', $p->{query} );
539 2         11 return { top_children => $p };
540             },
541             }
542 3         26 );
543             }
544              
545             #===================================
546             sub _unary_child {
547             #===================================
548 6     6   17 my ( $self, $type, $v ) = @_;
549             return $self->_SWITCH_refkind(
550             "Unary $type -has_child",
551             $v,
552             { HASHREF => sub {
553 4 100   4   38 my $p = $self->_hash_params(
554             'has_child', $v,
555             [ 'query', 'type' ],
556             $type eq 'query' ? [ 'boost', '_scope', 'score_type' ] : ['_scope']
557             );
558 4         36 $p->{query} = $self->_recurse( 'query', $p->{query} );
559 4         21 return { has_child => $p };
560             },
561             }
562 6         65 );
563             }
564              
565             #===================================
566             sub _unary_parent {
567             #===================================
568 6     6   17 my ( $self, $type, $v ) = @_;
569             return $self->_SWITCH_refkind(
570             "Unary $type -has_parent",
571             $v,
572             { HASHREF => sub {
573 4 100   4   41 my $p = $self->_hash_params(
574             'has_parent', $v,
575             [ 'query', 'type' ],
576             $type eq 'query' ? [ 'boost', '_scope', 'score_type' ] : ['_scope']
577             );
578 4         20 $p->{query} = $self->_recurse( 'query', $p->{query} );
579 4         21 return { has_parent => $p };
580             },
581             }
582 6         65 );
583             }
584              
585             #======================================================================
586             # Query only unary ops
587             #======================================================================
588              
589             #===================================
590             sub _query_unary_match {
591             #===================================
592 8     8   104 my ( $self, $v, $op ) = @_;
593 8   50     35 $op ||= 'match';
594             return $self->_SWITCH_refkind(
595             "Unary query -$op",
596             $v,
597             { HASHREF => sub {
598 2     2   19 my $p = $self->_hash_params(
599             $op, $v,
600             [ 'query', 'fields' ],
601             [ qw(
602             use_dis_max tie_breaker
603             boost operator analyzer fuzziness fuzzy_rewrite
604             rewrite max_expansions minimum_should_match
605             prefix_length lenient slop type)
606             ]
607             );
608 2         11 return { "multi_match" => $p };
609             },
610             }
611 8         62 );
612             }
613              
614             #===================================
615 8     8   24 sub _query_unary_qs { shift->_query_unary_query_string( @_, 'qs' ) }
616             #===================================
617              
618             #===================================
619             sub _query_unary_query_string {
620             #===================================
621 16     16   30 my ( $self, $v, $op ) = @_;
622 16   100     54 $op ||= 'query_string';
623             return $self->_SWITCH_refkind(
624             "Unary query -$op",
625             $v,
626 4     4   19 { SCALAR => sub { return { query_string => { query => $v } } },
627             HASHREF => sub {
628 4     4   30 my $p = $self->_hash_params(
629             'query_string',
630             $v,
631             ['query'],
632             [ qw(allow_leading_wildcard analyzer analyze_wildcard
633             auto_generate_phrase_queries boost
634             default_operator enable_position_increments
635             fields fuzzy_min_sim fuzzy_prefix_length
636             fuzzy_rewrite fuzzy_max_expansions
637             lowercase_expanded_terms phrase_slop
638             tie_breaker use_dis_max lenient
639             quote_analyzer quote_field_suffix
640             minimum_should_match )
641             ]
642             );
643 4         24 return { query_string => $p };
644             },
645             }
646 16         143 );
647             }
648              
649             #===================================
650             sub _query_unary_flt {
651             #===================================
652 8     8   14 my ( $self, $v ) = @_;
653             return $self->_SWITCH_refkind(
654             "Unary query -flt",
655             $v,
656 2     2   10 { SCALAR => sub { return { flt => { like_text => $v } } },
657             HASHREF => sub {
658 2     2   11 my $p = $self->_hash_params(
659             'flt', $v,
660             ['like_text'],
661             [ qw(analyzer boost fields ignore_tf max_query_terms
662             min_similarity prefix_length )
663             ]
664             );
665 2         10 return { flt => $p };
666             },
667             }
668 8         73 );
669             }
670              
671             #===================================
672             sub _query_unary_mlt {
673             #===================================
674 8     8   20 my ( $self, $v ) = @_;
675             return $self->_SWITCH_refkind(
676             "Unary query -mlt",
677             $v,
678 2     2   11 { SCALAR => sub { return { mlt => { like_text => $v } } },
679             HASHREF => sub {
680 2     2   15 my $p = $self->_hash_params(
681             'mlt', $v,
682             ['like_text'],
683             [ qw(analyzer boost boost_terms fields
684             max_doc_freq max_query_terms max_word_len
685             min_doc_freq min_term_freq min_word_len
686             percent_terms_to_match stop_words )
687             ]
688             );
689 2         10 return { mlt => $p };
690             },
691             }
692 8         73 );
693             }
694              
695             #===================================
696             sub _query_unary_custom_score {
697             #===================================
698 3     3   6 my ( $self, $v ) = @_;
699             return $self->_SWITCH_refkind(
700             "Unary query -custom_score",
701             $v,
702             { HASHREF => sub {
703 2     2   12 my $p = $self->_hash_params(
704             'custom_score', $v,
705             [ 'query', 'script' ],
706             [ 'params', 'lang' ]
707             );
708 2         10 $p->{query} = $self->_recurse( 'query', $p->{query} );
709 2         9 return { custom_score => $p };
710             },
711             }
712 3         24 );
713             }
714              
715             #===================================
716             sub _query_unary_constant_score {
717             #===================================
718 0     0   0 my ( $self, $v ) = @_;
719             return $self->_SWITCH_refkind(
720             "Unary query -constant_score",
721             $v,
722             { HASHREF => sub {
723 0     0   0 my $p = $self->_hash_params(
724             'constant_score', $v,
725             [ 'query' ],
726             [ 'boost' ]
727             );
728 0         0 $p->{query} = $self->_recurse( 'query', $p->{query} );
729 0         0 return { constant_score => $p };
730             },
731             }
732 0         0 );
733             }
734              
735              
736              
737             #===================================
738             sub _query_unary_custom_filters_score {
739             #===================================
740 2     2   6 my ( $self, $v ) = @_;
741             return $self->_SWITCH_refkind(
742             "Unary query -custom_filters_score",
743             $v,
744             { HASHREF => sub {
745 2     2   14 my $p = $self->_hash_params(
746             'custom_filters_score', $v,
747             [ 'query', 'filters' ],
748             [ 'score_mode', 'max_boost' ]
749             );
750 2         11 $p->{query} = $self->_recurse( 'query', $p->{query} );
751             my @raw
752 1         4 = ref $p->{filters} eq 'ARRAY'
753 2 100       11 ? @{ $p->{filters} }
754             : $p->{filters};
755 2         4 my @filters;
756              
757 2         5 for my $pf (@raw) {
758 3         15 $pf = $self->_hash_params( 'custom_filters_score.filters',
759             $pf, ['filter'],
760             [ 'boost', 'script', 'params', 'lang' ] );
761 3         12 $pf->{filter}
762             = $self->_recurse( 'filter', $pf->{filter} );
763 3         8 push @filters, $pf;
764             }
765 2         4 $p->{filters} = \@filters;
766 2         4 my $filters = $p->{filters};
767              
768 2         9 return { custom_filters_score => $p };
769             },
770             }
771 2         22 );
772             }
773              
774             #===================================
775 3     3   15 sub _query_unary_dismax { shift->_query_unary_dis_max(@_) }
776             #===================================
777             #===================================
778             sub _query_unary_dis_max {
779             #===================================
780 8     8   19 my ( $self, $v ) = @_;
781             return $self->_SWITCH_refkind(
782             "Unary query -dis_max",
783             $v,
784             { ARRAYREF => sub {
785 2     2   12 return $self->_query_unary_dis_max( { queries => $v } );
786             },
787             HASHREF => sub {
788 4     4   24 my $p = $self->_hash_params( 'dis_max', $v, ['queries'],
789             [ 'boost', 'tie_breaker' ] );
790 4         19 $p = $self->_multi_queries( $p, 'queries' );
791 4         27 return { dis_max => $p };
792             },
793             }
794 8         111 );
795             }
796              
797             #===================================
798             sub _query_unary_bool {
799             #===================================
800 5     5   10 my ( $self, $v ) = @_;
801             return $self->_SWITCH_refkind(
802             "Unary query -bool",
803             $v,
804             { HASHREF => sub {
805 4     4   25 my $p = $self->_hash_params(
806             'bool', $v,
807             [],
808             [ qw(must should must_not boost
809             minimum_number_should_match disable_coord)
810             ]
811             );
812 4         21 $p = $self->_multi_queries( $p, 'must', 'should',
813             'must_not' );
814 4         16 return { bool => $p };
815             },
816             }
817 5         40 );
818             }
819              
820             #===================================
821             sub _query_unary_boosting {
822             #===================================
823 4     4   11 my ( $self, $v ) = @_;
824             return $self->_SWITCH_refkind(
825             "Unary query -boosting",
826             $v,
827             { HASHREF => sub {
828 3     3   18 my $p = $self->_hash_params( 'boosting', $v,
829             [ 'positive', 'negative', 'negative_boost' ] );
830             $p->{$_} = $self->_recurse( 'query', $p->{$_} )
831 3         15 for 'positive', 'negative';
832 3         15 return { boosting => $p };
833             },
834             }
835 4         35 );
836             }
837              
838             #===================================
839             sub _query_unary_custom_boost {
840             #===================================
841 2     2   5 my ( $self, $v ) = @_;
842             return $self->_SWITCH_refkind(
843             "Unary query -custom_boost",
844             $v,
845             { HASHREF => sub {
846 1     1   8 my $p = $self->_hash_params( 'custom_boost', $v,
847             [ 'query', 'boost_factor' ] );
848 1         5 $p->{query} = $self->_recurse( 'query', $p->{query} );
849 1         5 return { custom_boost_factor => $p };
850             },
851             }
852 2         16 );
853             }
854              
855             #===================================
856             sub _query_unary_indices {
857             #===================================
858 6     6   13 my ( $self, $v ) = @_;
859             return $self->_SWITCH_refkind(
860             "Unary query -indices",
861             $v,
862             { HASHREF => sub {
863 5     5   28 my $p
864             = $self->_hash_params( 'indices', $v,
865             [ 'indices', 'query' ],
866             ['no_match_query'] );
867 5 50       30 $p->{indices} = [ $p->{indices} ]
868             unless ref $p->{indices} eq 'ARRAY';
869 5         19 $p->{query} = $self->_recurse( 'query', $p->{query} );
870 5         12 my $no = delete $p->{no_match_query};
871 5 100       18 if ($no) {
872 3 100       23 $p->{no_match_query}
873             = $no =~ /^(?:all|none)$/
874             ? $no
875             : $self->_recurse( 'query', $no );
876             }
877 5         18 return { indices => $p };
878             },
879             }
880 6         52 );
881             }
882              
883             #===================================
884             sub _query_unary_nested {
885             #===================================
886 3     3   8 my ( $self, $v ) = @_;
887             return $self->_SWITCH_refkind(
888             "Unary query -nested",
889             $v,
890             { HASHREF => sub {
891 2     2   16 my $p = $self->_hash_params(
892             'nested', $v,
893             [ 'path', 'query' ],
894             [ 'score_mode', '_scope' ]
895             );
896 2         11 $p->{query} = $self->_recurse( 'query', $p->{query} );
897 2         11 return { nested => $p };
898             },
899             }
900 3         32 );
901             }
902              
903             #======================================================================
904             # Filter only unary ops
905             #======================================================================
906              
907             #===================================
908             sub _filter_unary_missing {
909             #===================================
910 2     2   5 my ( $self, $v ) = @_;
911              
912             return $self->_SWITCH_refkind(
913             "Unary filter -missing",
914             $v,
915 1     1   5 { SCALAR => sub { return { missing => { field => $v } } },
916             HASHREF => sub {
917 1     1   8 my $p = $self->_hash_params( 'missing', $v, ['field'],
918             [ 'existence', 'null_value' ] );
919 1         5 return { missing => $p };
920             },
921             },
922 2         21 );
923             }
924              
925             #===================================
926             sub _filter_unary_exists {
927             #===================================
928 1     1   2 my ( $self, $v, $op ) = @_;
929 1   50     7 $op ||= 'exists';
930              
931             return $self->_SWITCH_refkind(
932             "Unary filter -$op",
933             $v,
934 1     1   6 { SCALAR => sub { return { $op => { field => $v } } }
935             }
936 1         9 );
937             }
938              
939             #===================================
940             sub _filter_unary_indices {
941             #===================================
942 6     6   11 my ( $self, $v ) = @_;
943             return $self->_SWITCH_refkind(
944             "Unary filter -indices",
945             $v,
946             { HASHREF => sub {
947 5     5   34 my $p
948             = $self->_hash_params( 'indices', $v,
949             [ 'indices', 'filter' ],
950             ['no_match_filter'] );
951 5 50       42 $p->{indices} = [ $p->{indices} ]
952             unless ref $p->{indices} eq 'ARRAY';
953 5         19 $p->{filter} = $self->_recurse( 'filter', $p->{filter} );
954 5         14 my $no = delete $p->{no_match_filter};
955 5 100       31 if ($no) {
956 3 100       24 $p->{no_match_filter}
957             = $no =~ /^(?:all|none)$/
958             ? $no
959             : $self->_recurse( 'filter', $no );
960             }
961 5         20 return { indices => $p };
962             },
963             }
964 6         63 );
965             }
966              
967             #===================================
968             sub _filter_unary_type {
969             #===================================
970 5     5   11 my ( $self, $v ) = @_;
971             return $self->_SWITCH_refkind(
972             "Unary filter -type",
973             $v,
974 2     2   11 { SCALAR => sub { return { type => { value => $v } } },
975             ARRAYREF => sub {
976 2     2   5 my @clauses = map { +{ type => { value => $_ } } } @$v;
  4         21  
977 2         10 return $self->_join_clauses( 'filter', 'or', \@clauses );
978             },
979             }
980 5         60 );
981             }
982              
983             #===================================
984             sub _filter_unary_limit {
985             #===================================
986 2     2   5 my ( $self, $v ) = @_;
987             return $self->_SWITCH_refkind(
988             "Unary filter -limit",
989             $v,
990 1     1   6 { SCALAR => sub { return { limit => { value => $v } } }
991             }
992 2         17 );
993             }
994              
995             #===================================
996             sub _filter_unary_script {
997             #===================================
998 3     3   8 my ( $self, $v ) = @_;
999             return $self->_SWITCH_refkind(
1000             "Unary filter -script",
1001             $v,
1002 1     1   6 { SCALAR => sub { return { script => { script => $v } } },
1003             HASHREF => sub {
1004 1     1   8 my $p = $self->_hash_params( 'script', $v, ['script'],
1005             [ 'params', 'lang' ] );
1006 1         5 return { script => $p };
1007             },
1008             }
1009 3         47 );
1010             }
1011              
1012             #===================================
1013             sub _filter_unary_nested {
1014             #===================================
1015 3     3   6 my ( $self, $v ) = @_;
1016             return $self->_SWITCH_refkind(
1017             "Filter query -nested",
1018             $v,
1019             { HASHREF => sub {
1020 2     2   15 my $p = $self->_hash_params(
1021             'nested', $v,
1022             [ 'path', 'filter' ],
1023             [ '_cache', '_name', '_cache_key' ],
1024             );
1025 2         11 $p->{filter} = $self->_recurse( 'filter', $p->{filter} );
1026 2         10 return { nested => $p };
1027             },
1028             }
1029 3         25 );
1030             }
1031              
1032             #===================================
1033 7     7   34 sub _filter_unary_query { shift->query(@_) }
1034             #===================================
1035              
1036             #===================================
1037 2     2   8 sub _filter_unary_nocache { shift->_filter_unary_cache( @_, 'nocache' ) }
1038             #===================================
1039             #===================================
1040             sub _filter_unary_cache {
1041             #===================================
1042 11     11   25 my ( $self, $v, $op ) = @_;
1043 11   100     76 $op ||= 'cache';
1044             my $filter = $self->_SWITCH_refkind(
1045             "Unary filter -$op",
1046             $v,
1047 3     3   11 { ARRAYREF => sub { $self->_recurse( 'filter', $v ) },
1048 8     8   34 HASHREF => sub { $self->_recurse( 'filter', $v ) },
1049             }
1050 11         126 );
1051              
1052 11 50       81 return unless $filter;
1053              
1054 11         36 my ($type) = grep { !/^_/ } keys %$filter;
  11         48  
1055 11 100 100     80 if ( $type eq 'query' ) {
    100          
1056 3         10 $filter = { fquery => $filter };
1057 3         8 $type = 'fquery';
1058             }
1059             elsif ( $type eq 'or' or $type eq 'and' ) {
1060 2         5 my $filters = $filter->{$type};
1061 2 50       13 $filter->{$type} = { filters => $filters } if ref $filters eq 'ARRAY';
1062             }
1063 11 100       53 $filter->{$type}{_cache} = $op eq 'cache' ? 1 : 0;
1064 11         27 return $filter;
1065             }
1066              
1067             #===================================
1068             sub _filter_unary_name {
1069             #===================================
1070 5     5   11 my ( $self, $v ) = @_;
1071             return $self->_SWITCH_refkind(
1072             "Unary filter -name",
1073             $v,
1074             { HASHREF => sub {
1075 5     5   8 my @filters;
1076 5         20 for my $name ( sort keys %$v ) {
1077 6 50       107 my $filter = $self->_recurse( 'filter', $v->{$name} )
1078             or next;
1079 6         16 my ($type) = grep { !/^_/ } keys %$filter;
  6         20  
1080 6 100       17 if ( $type eq 'query' ) {
1081 1         3 $filter = { fquery => $filter };
1082 1         2 $type = 'fquery';
1083             }
1084 6         13 $filter->{$type}{_name} = $name;
1085 6         18 push @filters, $filter;
1086             }
1087 5         13 return $self->_join_clauses( 'filter', 'or', \@filters );
1088             },
1089             }
1090 5         42 );
1091              
1092             }
1093              
1094             #===================================
1095             sub _filter_unary_cache_key {
1096             #===================================
1097 3     3   8 my ( $self, $v ) = @_;
1098             return $self->_SWITCH_refkind(
1099             "Unary filter -cache_key",
1100             $v,
1101 1     1   7 { HASHREF => sub { $self->_join_cache_keys( 'and', %$v ) },
1102 1     1   8 ARRAYREF => sub { $self->_join_cache_keys( 'or', @$v ) }
1103             }
1104 3         32 );
1105              
1106             }
1107              
1108             #===================================
1109             sub _join_cache_keys {
1110             #===================================
1111 2     2   7 my ( $self, $op, @v ) = @_;
1112 2         4 my @filters;
1113 2         8 while (@v) {
1114 4         7 my $key = shift @v;
1115 4 50       12 my $filter = $self->_recurse( 'filter', shift @v ) or next;
1116 4         13 my ($type) = grep { !/^_/ } keys %$filter;
  4         15  
1117 4 50       12 if ( $type eq 'query' ) {
1118 0         0 $filter = { fquery => $filter };
1119 0         0 $type = 'fquery';
1120             }
1121 4         9 $filter->{$type}{_cache_key} = $key;
1122 4         14 push @filters, $filter;
1123             }
1124 2         22 return $self->_join_clauses( 'filter', $op, \@filters );
1125             }
1126              
1127             #======================================================================
1128             # FIELD OPS
1129             #======================================================================
1130             # Query field ops
1131             #======================================================================
1132              
1133             #===================================
1134             sub _query_field_prefix {
1135             #===================================
1136 34     34   135 shift->_query_field_generic( @_, 'prefix', ['value'],
1137             [ 'boost', 'rewrite' ] );
1138             }
1139              
1140             #===================================
1141             sub _query_field_wildcard {
1142             #===================================
1143 51     51   201 shift->_query_field_generic( @_, 'wildcard', ['value'],
1144             [ 'boost', 'rewrite' ] );
1145             }
1146              
1147             #===================================
1148             sub _query_field_fuzzy {
1149             #===================================
1150 34     34   219 shift->_query_field_generic( @_, 'fuzzy', ['value'],
1151             [qw(boost min_similarity max_expansions prefix_length rewrite)] );
1152             }
1153              
1154             #===================================
1155             sub _query_field_match {
1156             #===================================
1157             shift->_query_field_generic(
1158 108     108   586 @_, 'match',
1159             ['query'],
1160             [ qw(boost operator analyzer
1161             fuzziness fuzzy_rewrite rewrite max_expansions
1162             minimum_should_match prefix_length lenient)
1163             ]
1164             );
1165             }
1166              
1167             #===================================
1168 16     16   42 sub _query_field_phrase { shift->_query_field_match_phrase(@_) }
1169 16     16   49 sub _query_field_phrase_prefix { shift->_query_field_match_phrase_prefix(@_) }
1170             #===================================
1171              
1172             #===================================
1173             sub _query_field_match_phrase {
1174             #===================================
1175 85     85   395 shift->_query_field_generic( @_, 'match_phrase', ['query'],
1176             [qw(boost analyzer slop lenient)] );
1177             }
1178              
1179             #===================================
1180             sub _query_field_match_phrase_prefix {
1181             #===================================
1182 97     97   485 shift->_query_field_generic( @_, 'match_phrase_prefix', ['query'],
1183             [qw(boost analyzer slop lenient max_expansions)] );
1184             }
1185              
1186             #===================================
1187 34     34   91 sub _query_field_qs { shift->_query_field_query_string(@_) }
1188             #===================================
1189              
1190             #===================================
1191             sub _query_field_query_string {
1192             #===================================
1193             shift->_query_field_generic(
1194 68     68   436 @_, 'field',
1195             ['query'],
1196             [ qw(default_operator analyzer allow_leading_wildcard
1197             lowercase_expanded_terms enable_position_increments
1198             fuzzy_prefix_length lenient fuzzy_min_sim
1199             fuzzy_rewrite fuzzy_max_expansions
1200             phrase_slop boost
1201             analyze_wildcard auto_generate_phrase_queries rewrite
1202             quote_analyzer quote_field_suffix
1203             minimum_should_match)
1204             ]
1205             );
1206             }
1207              
1208             #===================================
1209             sub _query_field_generic {
1210             #===================================
1211 477     477   1139 my ( $self, $k, $orig_op, $val, $op, $req, $opt ) = @_;
1212              
1213             return $self->_SWITCH_refkind(
1214             "Query field operator -$orig_op",
1215             $val,
1216 212     212   2449 { SCALAR => sub { return { $op => { $k => $val } } },
1217             ARRAYREF => sub {
1218 133   33 133   996 my $method
1219             = $self->can("_query_field_${op}")
1220             || $self->can("_query_field_${orig_op}")
1221             || croak
1222             "Couldn't find method _query_field_${op} or _query_field_${orig_op}";
1223             my @queries
1224 133         299 = map { $self->$method( $k, $orig_op, $_ ) } @$val;
  241         486  
1225 55         261 return $self->_join_clauses( 'query', 'or', \@queries ),;
1226             },
1227             HASHREF => sub {
1228 28     28   114 my $p = $self->_hash_params( $orig_op, $val, $req, $opt );
1229 27         160 return { $op => { $k => $p } };
1230             },
1231             }
1232 477         5305 );
1233             }
1234              
1235             #===================================
1236 18     18   46 sub _query_field_term { shift->_query_field_terms(@_) }
1237             #===================================
1238              
1239             #===================================
1240             sub _query_field_terms {
1241             #===================================
1242 64     64   137 my ( $self, $k, $op, $val ) = @_;
1243              
1244             return $self->_SWITCH_refkind(
1245             "Query field operator -$op",
1246             $val,
1247 20     20   241 { SCALAR => sub { return { term => { $k => $val } } },
1248             HASHREF => sub {
1249 8     8   39 $val = {%$val};
1250 8         21 my $v = delete $val->{value};
1251 8 50 66     43 $v = $v->[0] if ref $v eq 'ARRAY' and @$v < 2;
1252 8 50       20 croak "Missing 'value' param in 'terms' query"
1253             unless defined $v;
1254              
1255 8 100       27 if ( ref $v eq 'ARRAY' ) {
1256 4         17 my $p = $self->_hash_params( $op, $val, [],
1257             [ 'boost', 'minimum_match' ] );
1258 4         12 $p->{$k} = $v;
1259 4         15 return { terms => $p };
1260             }
1261 4         7 delete $val->{minimum_match};
1262 4         19 my $p = $self->_hash_params( $op, $val, [], ['boost'] );
1263 4         10 $p->{value} = $v;
1264 4         19 return { term => { $k => $p } };
1265             },
1266             ARRAYREF => sub {
1267 20 100   20   39 my @scalars = grep { defined && !ref } @$val;
  36         178  
1268 20 100 100     76 if ( @scalars == @$val && @scalars > 1 ) {
1269 4         23 return { terms => { $k => $val } };
1270             }
1271 16         22 my @queries;
1272 16         28 for (@$val) {
1273 28 50       72 my $query = $self->_query_field_terms( $k, $op, $_ )
1274             or next;
1275 16         40 push @queries, $query;
1276             }
1277 4         16 return $self->_join_clauses( 'query', 'or', \@queries );
1278             },
1279              
1280             },
1281 64         751 );
1282             }
1283              
1284             #===================================
1285             sub _query_field_mlt {
1286             #===================================
1287 34     34   70 my ( $self, $k, $op, $val ) = @_;
1288              
1289             return $self->_SWITCH_refkind(
1290             "Query field operator -$op",
1291             $val,
1292             { SCALAR => sub {
1293 14     14   149 return { mlt_field => { $k => { like_text => $val } } };
1294             },
1295             ARRAYREF => sub {
1296             my @queries
1297 10     10   18 = map { $self->_query_field_mlt( $k, $op, $_ ) } @$val;
  18         44  
1298 4         13 return $self->_join_clauses( 'query', 'or', \@queries ),;
1299             },
1300             HASHREF => sub {
1301 2     2   15 my $p = $self->_hash_params(
1302             $op, $val,
1303             ['like_text'],
1304             [ qw( analyzer boost boost_terms max_doc_freq
1305             max_query_terms max_word_len min_doc_freq
1306             min_term_freq min_word_len
1307             percent_terms_to_match stop_words )
1308             ]
1309             );
1310 2         12 return { mlt_field => { $k => $p } };
1311             },
1312             }
1313 34         327 );
1314             }
1315              
1316             #===================================
1317             sub _query_field_flt {
1318             #===================================
1319 34     34   76 my ( $self, $k, $op, $val ) = @_;
1320              
1321             return $self->_SWITCH_refkind(
1322             "Query field operator -$op",
1323             $val,
1324             { SCALAR => sub {
1325 14     14   145 return { flt_field => { $k => { like_text => $val } } };
1326             },
1327             ARRAYREF => sub {
1328             my @queries
1329 10     10   19 = map { $self->_query_field_flt( $k, $op, $_ ) } @$val;
  18         43  
1330 4         13 return $self->_join_clauses( 'query', 'or', \@queries ),;
1331             },
1332             HASHREF => sub {
1333 2     2   11 my $p = $self->_hash_params(
1334             $op, $val,
1335             ['like_text'],
1336             [ qw( analyzer boost ignore_tf max_query_terms
1337             min_similarity prefix_length)
1338             ]
1339             );
1340 2         11 return { flt_field => { $k => $p } };
1341             },
1342             }
1343 34         323 );
1344             }
1345              
1346             #===================================
1347             sub _query_field_range {
1348             #===================================
1349 91     91   198 my ( $self, $k, $op, $val ) = @_;
1350              
1351 91   66     335 my $es_op = $RANGE_MAP{$op} || $op;
1352 91 100       377 if ( $op eq 'range' ) {
1353             return $self->_SWITCH_refkind(
1354             "Query field operator -range",
1355             $val,
1356             { HASHREF => sub {
1357 1     1   8 my $p = $self->_hash_params(
1358             'range', $val,
1359             [],
1360             [ qw(from to include_lower include_upper
1361             gt gte lt lte boost)
1362             ]
1363             );
1364 1         8 return { range => { $k => $p } };
1365             },
1366             SCALAR => sub {
1367 0     0   0 croak "range op does not accept a scalar. Instead, use "
1368             . "a comparison operator, eg: gt, lt";
1369             },
1370             }
1371 1         16 );
1372             }
1373              
1374             return $self->_SWITCH_refkind(
1375             "Query field operator -$op",
1376             $val,
1377             { SCALAR => sub {
1378 42     42   222 return { 'range' => { $k => { $es_op => $val } } };
1379             },
1380             }
1381 90         849 );
1382             }
1383              
1384             #======================================================================
1385             # Filter field ops
1386             #======================================================================
1387              
1388             #===================================
1389 16     16   61 sub _filter_field_term { shift->_filter_field_terms(@_) }
1390             #===================================
1391              
1392             #===================================
1393             sub _filter_field_terms {
1394             #===================================
1395 123     123   351 my ( $self, $k, $op, $val ) = @_;
1396              
1397             return $self->_SWITCH_refkind(
1398             "Filter field operator -$op",
1399             $val,
1400 28     28   100 { UNDEF => sub { $self->_hashpair_UNDEF( 'filter', $k, $val ) },
1401 46     46   158 SCALAR => sub { $self->_hashpair_SCALAR( 'filter', $k, $val ) },
1402             ARRAYREF => sub {
1403 43 100   43   115 my @scalars = grep { defined && !ref } @$val;
  79         415  
1404 43 100 100     204 if ( @scalars == @$val && @scalars > 1 ) {
1405 14         104 return { terms => { $k => $val } };
1406             }
1407 29         48 my @filters;
1408 29         58 for (@$val) {
1409 50 50       129 my $filter = $self->_filter_field_terms( $k, $op, $_ )
1410             or next;
1411 50         124 push @filters, $filter;
1412             }
1413 29         97 return $self->_join_clauses( 'filter', 'or', \@filters );
1414             },
1415             HASHREF => sub {
1416 6     6   29 $val = {%$val};
1417 6         19 my $v = delete $val->{value};
1418              
1419 6 100 66     45 $v = $v->[0] if ref $v eq 'ARRAY' and @$v < 2;
1420 6 50       15 croak "Missing 'value' param in 'terms' filter"
1421             unless defined $v;
1422              
1423 6 100       18 if ( ref $v eq 'ARRAY' ) {
1424 3         18 my $p
1425             = $self->_hash_params( $op, $val, [], ['execution'] );
1426 3         10 $p->{$k} = $v;
1427 3         12 return { terms => $p };
1428             }
1429 3         15 return { term => { $k => $v } };
1430             },
1431             }
1432 123         1909 );
1433             }
1434              
1435             #===================================
1436             sub _filter_field_range {
1437             #===================================
1438 94     94   184 my ( $self, $k, $op, $val ) = @_;
1439              
1440 94         110 my ( $type, $es_op );
1441 94 100       264 if ( $es_op = $RANGE_MAP{$op} ) {
1442 52         73 $type = 'numeric_range';
1443             }
1444             else {
1445 42         63 $es_op = $op;
1446 42         62 $type = 'range';
1447             }
1448              
1449 94 50       221 if ( $op eq 'range' ) {
1450             return $self->_SWITCH_refkind(
1451             "Filter field operator -$op",
1452             $val,
1453             { HASH => sub {
1454 0     0   0 my $p = $self->_hash_params(
1455             'range', $val,
1456             [],
1457             [ qw(from to include_lower include_upper
1458             gt gte lt lte boost)
1459             ]
1460             );
1461 0         0 return { range => { $k => $p } };
1462             },
1463             }
1464 0         0 );
1465             }
1466             return $self->_SWITCH_refkind(
1467             "Filter field operator -$op",
1468             $val,
1469             { SCALAR => sub {
1470 46     46   247 return { $type => { $k => { $es_op => $val } } };
1471             },
1472             }
1473 94         804 );
1474             }
1475              
1476             #===================================
1477             sub _filter_field_prefix {
1478             #===================================
1479 60     60   121 my ( $self, $k, $op, $val ) = @_;
1480              
1481             return $self->_SWITCH_refkind(
1482             "Filter field operator -$op",
1483             $val,
1484 33     33   257 { SCALAR => sub { return { prefix => { $k => $val } } },
1485             ARRAYREF => sub {
1486             my @filters
1487 15     15   31 = map { $self->_filter_field_prefix( $k, $op, $_ ) }
  27         72  
1488             @$val;
1489 6         22 return $self->_join_clauses( 'filter', 'or', \@filters ),;
1490             },
1491             }
1492 60         598 );
1493             }
1494              
1495             #===================================
1496             sub _filter_field_exists {
1497             #===================================
1498 6     6   14 my ( $self, $k, $op, $val ) = @_;
1499 6   100     30 $val ||= 0;
1500 6 100       27 $val = !$val if $op =~ s/^not_//;
1501              
1502             return $self->_SWITCH_refkind(
1503             "Filter field operator -$op",
1504             $val,
1505             { SCALAR => sub {
1506 6 50   6   16 if ( $op eq 'missing' ) { $val = !$val }
  0         0  
1507 6 100       41 return { ( $val ? 'exists' : 'missing' ) => { field => $k } };
1508             },
1509             }
1510 6         50 );
1511             }
1512              
1513             #===================================
1514             sub _filter_field_missing {
1515             #===================================
1516 8     8   22 my ( $self, $k, $op, $val ) = @_;
1517 8   100     34 $val ||= 0;
1518              
1519             return $self->_SWITCH_refkind(
1520             "Filter field operator -$op",
1521             $val,
1522             { SCALAR => sub {
1523 6 100   6   42 return { ( $val ? 'missing' : 'exists' ) => { field => $k } };
1524             },
1525             HASHREF => sub {
1526 2     2   12 my $p = $self->_hash_params( 'missing', $val, [],
1527             [ 'null_value', 'existence' ] );
1528 2         6 $p->{field} = $k;
1529 2         8 return { missing => $p };
1530             },
1531              
1532             }
1533 8         92 );
1534             }
1535              
1536             #===================================
1537             sub _filter_field_geo_bbox {
1538             #===================================
1539 2     2   11 shift->_filter_field_geo_bounding_box( $_[0], 'geo_bbox', $_[2] );
1540             }
1541              
1542             #===================================
1543             sub _filter_field_geo_bounding_box {
1544             #===================================
1545 5     5   9 my $self = shift;
1546 5         8 my $k = shift;
1547 5         31 my $p = $self->_hash_params(
1548             @_,
1549             [qw(top_left bottom_right)],
1550             [ 'normalize', 'type' ]
1551             );
1552 3         20 return { geo_bounding_box => { $k => $p } };
1553             }
1554              
1555             #===================================
1556             sub _filter_field_geo_distance {
1557             #===================================
1558 4     4   10 my $self = shift;
1559 4         7 my $k = shift;
1560 4         39 my $p = $self->_hash_params( @_, [qw(distance location )],
1561             [ 'normalize', 'optimize_bbox' ] );
1562 2         8 $p->{$k} = delete $p->{location};
1563 2         7 return { geo_distance => $p };
1564             }
1565              
1566             #===================================
1567             sub _filter_field_geo_distance_range {
1568             #===================================
1569 2     2   5 my $self = shift;
1570 2         4 my $k = shift;
1571 2         14 my $p = $self->_hash_params(
1572             @_,
1573             ['location'],
1574             [ qw(from to gt lt gte lte
1575             include_upper include_lower normalize optimize_bbox)
1576             ]
1577             );
1578 2         8 $p->{$k} = delete $p->{location};
1579 2         9 return { geo_distance_range => $p };
1580             }
1581              
1582             #===================================
1583             sub _filter_field_geo_polygon {
1584             #===================================
1585 5     5   12 my $self = shift;
1586 5         7 my $k = shift;
1587 5         11 my ( $op, $val ) = @_;
1588              
1589             return $self->_SWITCH_refkind(
1590             "Filter field operator -$op",
1591             $val,
1592             { ARRAYREF => sub {
1593 2     2   14 return { geo_polygon => { $k => { points => $val } } };
1594             },
1595             HASHREF => sub {
1596 2     2   14 my $p = $self->_hash_params( $op, $val, ['points'],
1597             ['normalize'] );
1598 2         12 return { geo_polygon => { $k => $p } };
1599             },
1600             }
1601 5         104 );
1602             }
1603              
1604             #======================================================================
1605             # UTILITIES
1606             #======================================================================
1607              
1608             #===================================
1609             sub _top_recurse {
1610             #===================================
1611 834     834   2478 my $self = shift;
1612 834         1546 my $type = shift;
1613 834         1287 my $params = shift;
1614 834 100       2888 croak "Too many params passed to ${type}()"
1615             if @_;
1616 832         2543 my $clause = $self->_recurse( $type, $params );
1617 500 100       3654 return $clause ? { $type => $clause } : undef;
1618             }
1619              
1620             #===================================
1621             sub _recurse {
1622             #===================================
1623 1320     1320   2658 my ( $self, $type, $params, $logic ) = @_;
1624              
1625 1320         12728 my $method = $self->_METHOD_FOR_refkind( "_top", $params );
1626 1320         3736 return $self->$method( $type, $params, $logic );
1627             }
1628              
1629             #===================================
1630             sub _refkind {
1631             #===================================
1632 3943     3943   6158 my ( $self, $data ) = @_;
1633              
1634 3943 100       8844 return 'UNDEF' unless defined $data;
1635              
1636             # blessed objects are treated like scalars
1637 3716 50       24059 my $ref = ( Scalar::Util::blessed $data) ? '' : ref $data;
1638              
1639 3716 100       9883 return 'SCALAR' unless $ref;
1640              
1641 2708         3199 my $n_steps = 1;
1642 2708         7449 while ( $ref eq 'REF' ) {
1643 6         13 $data = $$data;
1644 6 50       20 $ref = ( Scalar::Util::blessed $data) ? '' : ref $data;
1645 6 50       21 $n_steps++ if $ref;
1646             }
1647              
1648 2708   50     12948 return ( $ref || 'SCALAR' ) . ( 'REF' x $n_steps );
1649             }
1650              
1651             #===================================
1652             sub _try_refkind {
1653             #===================================
1654 3943     3943   5700 my ( $self, $data ) = @_;
1655 3943         7606 my @try = ( $self->_refkind($data) );
1656 3943 100 100     19743 push @try, 'SCALAR_or_UNDEF'
1657             if $try[0] eq 'SCALAR' || $try[0] eq 'UNDEF';
1658 3943         5280 push @try, 'FALLBACK';
1659 3943         11661 return \@try;
1660             }
1661              
1662             #===================================
1663             sub _METHOD_FOR_refkind {
1664             #===================================
1665 2293     2293   3582 my ( $self, $meth_prefix, $data ) = @_;
1666              
1667 2293         3197 my $method;
1668 2293         2840 for ( @{ $self->_try_refkind($data) } ) {
  2293         4861  
1669 2293 50       13817 $method = $self->can( $meth_prefix . "_" . $_ )
1670             and last;
1671             }
1672              
1673 2293   33     15210 return $method
1674             || croak "cannot dispatch on '$meth_prefix' for "
1675             . $self->_refkind($data);
1676             }
1677              
1678             #===================================
1679             sub _SWITCH_refkind {
1680             #===================================
1681 1650     1650   3506 my ( $self, $op, $data, $dispatch_table ) = @_;
1682              
1683 1650         3183 my $coderef;
1684 1650         1914 for ( @{ $self->_try_refkind($data) } ) {
  1650         3364  
1685 2130 100       6272 $coderef = $dispatch_table->{$_}
1686             and last;
1687             }
1688              
1689 1650 100       4592 unless ($coderef) {
1690 286         6072 croak "$op only accepts parameters of type: "
1691             . join( ', ', sort keys %$dispatch_table ) . "\n";
1692             }
1693              
1694 1364         2582 return $coderef->();
1695             }
1696              
1697             #===================================
1698             sub _hash_params {
1699             #===================================
1700 132     132   312 my ( $self, $op, $val, $req, $opt ) = @_;
1701              
1702 132 100       540 croak "Op '$op' only accepts a hashref"
1703             unless ref $val eq 'HASH';
1704 128         890 $val = {%$val};
1705 128         278 my %params;
1706 128         329 for (@$req) {
1707 142         565 my $v = $params{$_} = delete $val->{$_};
1708 142 100 66     1138 croak "'$op' missing required param '$_'"
1709             unless defined $v and length $v;
1710             }
1711 127 100       456 if ($opt) {
1712 123         283 for (@$opt) {
1713 559 100       1278 next unless exists $val->{$_};
1714 495         850 my $val = delete $val->{$_};
1715 495 100 100     3141 $params{$_} = defined($val) && length($val) ? $val : '';
1716             }
1717             }
1718              
1719 127 100       487 croak "Unknown param(s) for '$op': " . join( ', ', keys %$val )
1720             if %$val;
1721              
1722 126         384 return \%params;
1723             }
1724              
1725             #===================================
1726             sub _multi_queries {
1727             #===================================
1728 8     8   16 my $self = shift;
1729 8         15 my $params = shift;
1730 8         18 for my $key (@_) {
1731 16 100       53 my $v = delete $params->{$key} or next;
1732 15 100       135 my @q = ref $v eq 'ARRAY' ? @$v : $v;
1733 15         31 my @queries = map { $self->_recurse( 'query', $_ ) } @q;
  24         56  
1734 15 100       44 next unless @queries;
1735 14         51 $params->{$key} = \@queries;
1736             }
1737 8         19 return $params;
1738             }
1739              
1740             1;
1741              
1742             =head1 NAME
1743              
1744             ElasticSearch::SearchBuilder - A Perlish compact query language for ElasticSearch
1745              
1746             =head1 VERSION
1747              
1748             Version 0.16
1749              
1750             Compatible with ElasticSearch version 0.19.11
1751              
1752             =cut
1753              
1754             =head1 BREAKING CHANGE
1755              
1756             The 'text' queries have been renamed 'match' queries in
1757             elasticsearch 0.19.9. If you need support for an older version of elasticsearch,
1758             please use L<https://metacpan.org/release/DRTECH/ElasticSearch-SearchBuilder-0.15/>.
1759              
1760             =head1 DESCRIPTION
1761              
1762             The Query DSL for ElasticSearch (see L<Query DSL|http://www.elasticsearch.org/guide/reference/query-dsl>),
1763             which is used to write queries and filters,
1764             is simple but verbose, which can make it difficult to write and understand
1765             large queries.
1766              
1767             L<ElasticSearch::SearchBuilder> is an L<SQL::Abstract>-like query language
1768             which exposes the full power of the query DSL, but in a more compact,
1769             Perlish way.
1770              
1771             B<This module is considered stable.> If you have
1772             suggestions for improvements to the API or the documenation, please
1773             contact me.
1774              
1775             =cut
1776              
1777             =head1 SYNOPSIS
1778              
1779             my $sb = ElasticSearch::SearchBuilder->new();
1780             my $query = $sb->query({
1781             body => 'interesting keywords',
1782             -filter => {
1783             status => 'active',
1784             tags => ['perl','python','ruby'],
1785             created => {
1786             '>=' => '2010-01-01',
1787             '<' => '2011-01-01'
1788             },
1789             }
1790             })
1791              
1792              
1793             B<NOTE>: C<ElasticSearch::SearchBuilder> is fully integrated with the
1794             L<ElasticSearch> API. Wherever you can specify C<query>, C<filter> or
1795             C<facet_filter> in L<ElasticSearch>, you can automatically use SearchBuilder
1796             by specifying C<queryb>, C<filterb>, C<facet_filterb> instead.
1797              
1798             $es->search( queryb => { body => 'interesting keywords' } )
1799              
1800             =cut
1801              
1802             =head1 METHODS
1803              
1804             =head2 new()
1805              
1806             my $sb = ElasticSearch::SearchBuilder->new()
1807              
1808             Creates a new instance of the SearchBuilder - takes no parameters.
1809              
1810             =head2 query()
1811              
1812             my $es_query = $sb->query($compact_query)
1813              
1814             Returns a query in the ElasticSearch query DSL.
1815              
1816             C<$compact_query> can be a scalar, a hash ref or an array ref.
1817              
1818             $sb->query('foo')
1819             # { "query" : { "match" : { "_all" : "foo" }}}
1820              
1821             $sb->query({ ... }) or $sb->query([ ... ])
1822             # { "query" : { ... }}
1823              
1824             =head2 filter()
1825              
1826             my $es_filter = $sb->filter($compact_filter)
1827              
1828             Returns a filter in the ElasticSearch query DSL.
1829              
1830             C<$compact_filter> can be a scalar, a hash ref or an array ref.
1831              
1832             $sb->filter('foo')
1833             # { "filter" : { "term" : { "_all" : "foo" }}}
1834              
1835             $sb->filter({ ... }) or $sb->filter([ ... ])
1836             # { "filter" : { ... }}
1837              
1838             =cut
1839              
1840             =head1 INTRODUCTION
1841              
1842             B<IMPORTANT>: If you are not familiar with ElasticSearch then you should
1843             read L</"ELASTICSEARCH CONCEPTS"> before continuing.
1844              
1845             This module was inspired by L<SQL::Abstract> but they are not compatible with
1846             each other.
1847              
1848             The easiest way to explain how the syntax works is to give examples:
1849              
1850             =head2 QUERY / FILTER CONTEXT
1851              
1852             There are two contexts:
1853              
1854             =over
1855              
1856             =item *
1857              
1858             C<filter> context
1859              
1860             Filter are fast and cacheable. They should be used to include/exclude docs,
1861             based on simple term values. For instance, exclude all docs that have
1862             neither tag C<perl> nor C<python>.
1863              
1864             Typically, most of your clauses should be filters, which reduce the number
1865             of docs that need to be passed to the query.
1866              
1867             =item *
1868              
1869             C<query> context
1870              
1871             Queries are smarter than filters, but more expensive, as they have
1872             to calculate search relevance (ie C<_score>).
1873              
1874             They should be used where:
1875              
1876             =over
1877              
1878             =item *
1879              
1880             relevance is important, eg: in a search for tags C<perl> or C<python>,
1881             a doc that has BOTH tags is more relevant than a doc that has only one
1882              
1883             =item *
1884              
1885             where search terms need to be analyzed as full text, eg: find me all
1886             docs where the C<content> field includes the words "Perl is GREAT", no matter
1887             how those words are capitalized.
1888              
1889             =back
1890              
1891             =back
1892              
1893             The available operators (and the query/filter clauses that are generated)
1894             differ according to which context you are in.
1895              
1896             The initial context depends upon which method you use: L</"query()"> puts
1897             you into C<query> context, and L</"filter()"> into C<filter> context.
1898              
1899             However, you can switch from one context to another as follows:
1900              
1901             $sb->query({
1902              
1903             # query context
1904             foo => 1,
1905             bar => 2,
1906              
1907             -filter => {
1908             # filter context
1909             foo => 1,
1910             bar => 2,
1911              
1912             -query => {
1913             # query context
1914             foo => 1
1915             }
1916             }
1917             })
1918              
1919              
1920             =head3 -filter | -not_filter
1921              
1922             Switch from query context to filter context:
1923              
1924             # query field content for 'brown cow', and filter documents
1925             # where status is 'active' and tags contains the term 'perl'
1926             {
1927             content => 'brown cow',
1928             -filter => {
1929             status => 'active',
1930             tags => 'perl'
1931             }
1932             }
1933              
1934              
1935             # no query, just a filter:
1936             { -filter => { status => 'active' }}
1937              
1938             See L<Filtered Query|http://www.elasticsearch.org/guide/reference/query-dsl/filtered-query.html>
1939             and L<Constant Score Query|http://www.elasticsearch.org/guide/reference/query-dsl/constant-score-query.html>
1940              
1941             =head3 -query | -not_query
1942              
1943             Use a query as a filter:
1944              
1945             # query field content for 'brown cow', and filter documents
1946             # where status is 'active', tags contains the term 'perl'
1947             # and a match query on field title contains 'important'
1948             {
1949             content => 'brown cow',
1950             -filter => {
1951             status => 'active',
1952             tags => 'perl',
1953             -query => {
1954             title => 'important'
1955             }
1956             }
1957             }
1958              
1959             See L<Query Filter|http://www.elasticsearch.org/guide/reference/query-dsl/query-filter.html>
1960              
1961             =head2 KEY-VALUE PAIRS
1962              
1963             Key-value pairs are equivalent to the C<=> operator, discussed below. They are
1964             converted to C<match> queries or C<term> filters:
1965              
1966             # Field 'foo' contains term 'bar'
1967             # equiv: { foo => { '=' => 'bar' }}
1968             { foo => 'bar' }
1969              
1970              
1971              
1972             # Field 'foo' contains 'bar' or 'baz'
1973             # equiv: { foo => { '=' => ['bar','baz'] }}
1974             { foo => ['bar','baz']}
1975              
1976              
1977             # Field 'foo' contains terms 'bar' AND 'baz'
1978             # equiv: { foo => { '-and' => [ {'=' => 'bar'}, {'=' => 'baz'}] }}
1979             { foo => ['-and','bar','baz']}
1980              
1981              
1982             ### FILTER ONLY ###
1983              
1984             # Field 'foo' is missing ie has no value
1985             # equiv: { -missing => 'foo' }
1986             { foo => undef }
1987              
1988             =cut
1989              
1990             =head2 AND|OR LOGIC
1991              
1992             Arrays are OR'ed, hashes are AND'ed:
1993              
1994             # tags = 'perl' AND status = 'active:
1995             {
1996             tags => 'perl',
1997             status => 'active'
1998             }
1999              
2000             # tags = 'perl' OR status = 'active:
2001             [
2002             tags => 'perl',
2003             status => 'active'
2004             ]
2005              
2006             # tags = 'perl' or tags = 'python':
2007             { tags => [ 'perl','python' ]}
2008             { tags => { '=' => [ 'perl','python' ] }}
2009              
2010             # tags begins with prefix 'p' or 'r'
2011             { tags => { '^' => [ 'p','r' ] }}
2012              
2013             The logic in an array can changed from C<OR> to C<AND> by making the first
2014             element of the array ref C<-and>:
2015              
2016             # tags has term 'perl' AND 'python'
2017              
2018             { tags => ['-and','perl','python']}
2019              
2020             {
2021             tags => [
2022             -and => { '=' => 'perl'},
2023             { '=' => 'python'}
2024             ]
2025             }
2026              
2027             However, the first element in an array ref which is used as the value for
2028             a field operator (see L</"FIELD OPERATORS">) is not special:
2029              
2030             # WRONG
2031             { tags => { '=' => [ '-and','perl','python' ] }}
2032              
2033             # RIGHT
2034             { tags => ['-and' => [ {'=' => 'perl'}, {'=' => 'python'} ] ]}
2035              
2036             ...otherwise you would never be able to search for the term C<-and>. So if
2037             you might possibly have the terms C<-and> or C<-or> in your data, use:
2038              
2039             { foo => {'=' => [....] }}
2040              
2041             instead of:
2042              
2043             { foo => [....]}
2044              
2045             =head3 -and | -or | -not
2046              
2047             These unary operators allow you apply C<and>, C<or> and C<not> logic to
2048             nested queries or filters.
2049              
2050             # Field foo has both terms 'bar' and 'baz'
2051             { -and => [
2052             foo => 'bar',
2053             foo => 'baz'
2054             ]}
2055              
2056             # Field 'name' contains 'john smith', or the name field is missing
2057             # and the 'desc' field contains 'john smith'
2058              
2059             { -or => [
2060             { name => 'John Smith' },
2061             {
2062             desc => 'John Smith'
2063             -filter => { -missing => 'name' },
2064             }
2065             ]}
2066              
2067             The C<-and>, C<-or> and C<-not> constructs emit C<bool> queries when
2068             in query context, and C<and>, C<or> and C<not> clauses when in filter
2069             context.
2070              
2071             See also:
2072             L</"NAMED FILTERS">,
2073             L<Bool Query|http://www.elasticsearch.org/guide/reference/query-dsl/bool-query.html>,
2074             L<And Filter|http://www.elasticsearch.org/guide/reference/query-dsl/and-filter.html>,
2075             L<Or Filter|http://www.elasticsearch.org/guide/reference/query-dsl/or-filter.html>
2076             and
2077             L<Not Filter|http://www.elasticsearch.org/guide/reference/query-dsl/not-filter.html>
2078              
2079              
2080             =head2 FIELD OPERATORS
2081              
2082             Most operators (eg C<=>, C<gt>, C<geo_distance> etc) are applied to a
2083             particular field. These are known as C<Field Operators>. For example:
2084              
2085             # Field foo contains the term 'bar'
2086             { foo => 'bar' }
2087             { foo => {'=' => 'bar' }}
2088              
2089             # Field created is between Jan 1 and Dec 31 2010
2090             { created => {
2091             '>=' => '2010-01-01',
2092             '<' => '2011-01-01'
2093             }}
2094              
2095             # Field foo contains terms which begin with prefix 'a' or 'b' or 'c'
2096             { foo => { '^' => ['a','b','c' ]}}
2097              
2098             Some field operators are available as symbols (eg C<=>, C<*>, C<^>, C<gt>) and
2099             others as words (eg C<geo_distance> or C<-geo_distance> - the dash is optional).
2100              
2101             Multiple field operators can be applied to a single field.
2102             Use C<{}> to imply C<this AND that>:
2103              
2104             # Field foo has any value from 100 to 200
2105             { foo => { gte => 100, lte => 200 }}
2106              
2107             # Field foo begins with 'p' but is not python
2108             { foo => {
2109             '^' => 'p',
2110             '!=' => 'python'
2111             }}
2112              
2113             Or C<[]> to imply C<this OR that>
2114              
2115             # foo is 5 or foo greater than 10
2116             { foo => [
2117             { '=' => 5 },
2118             { 'gt' => 10 }
2119             ]}
2120              
2121             All word operators may be negated by adding C<not_> to the beginning, eg:
2122              
2123             # Field foo does NOT contain a term beginning with 'bar' or 'baz'
2124             { foo => { not_prefix => ['bar','baz'] }}
2125              
2126              
2127             =head2 UNARY OPERATORS
2128              
2129             There are other operators which don't fit this
2130             C<< { field => { op => value }} >> model.
2131              
2132             For instance:
2133              
2134             =over
2135              
2136             =item *
2137              
2138             An operator might apply to multiple fields:
2139              
2140             # Search fields 'title' and 'content' for text 'brown cow'
2141             {
2142             -match => {
2143             query => 'brown cow',
2144             fields => ['title','content']
2145             }
2146             }
2147              
2148             =item *
2149              
2150             The field might BE the value:
2151              
2152             # Find documents where the field 'foo' is blank or undefined
2153             { -missing => 'foo' }
2154              
2155             # Find documents where the field 'foo' exists and has a value
2156             { -exists => 'foo' }
2157              
2158             =item *
2159              
2160             For combining other queries or filters:
2161              
2162             # Field foo has terms 'bar' and 'baz' but not 'balloo'
2163             {
2164             -and => [
2165             foo => 'bar',
2166             foo => 'baz',
2167             -not => { foo => 'balloo' }
2168             ]
2169             }
2170              
2171             =item *
2172              
2173             Other:
2174              
2175             # Script query
2176             { -script => "doc['num1'].value > 1" }
2177              
2178             =back
2179              
2180             These operators are called C<unary operators> and ALWAYS begin with a dash C<->
2181             to distinguish them from field names.
2182              
2183             Unary operators may also be prefixed with C<not_> to negate their meaning.
2184              
2185             =cut
2186              
2187             =head1 MATCH ALL
2188              
2189             =head2 -all
2190              
2191             The C<-all> operator matches all documents:
2192              
2193             # match all
2194             { -all => 1 }
2195             { -all => 0 }
2196             { -all => {} }
2197              
2198             In query context, the C<match_all> query usually scores all docs as
2199             1 (ie having the same relevance). By specifying a C<norms_field>, the
2200             relevance can be read from that field (at the cost of a slower execution time):
2201              
2202             # Query context only
2203             { -all =>{
2204             boost => 1,
2205             norms_field => 'doc_boost'
2206             }}
2207              
2208             =head1 EQUALITY
2209              
2210             These operators answer the question: "Does this field contain this term?"
2211              
2212             Filter equality operators work only with exact terms, while query equality
2213             operators (the C<match> family of queries) will "do the right thing", ie
2214             work with terms for C<not_analyzed> fields and with analyzed text for
2215             C<analyzed> fields.
2216              
2217             =head2 EQUALITY (QUERIES)
2218              
2219             =head3 = | -match | != | <> | -not_match
2220              
2221             These operators all generate C<match> queries:
2222              
2223             # Analyzed field 'title' contains the terms 'Perl is GREAT'
2224             # (which is analyzed to the terms 'perl','great')
2225             { title => 'Perl is GREAT' }
2226             { title => { '=' => 'Perl is GREAT' }}
2227             { title => { match => 'Perl is GREAT' }}
2228              
2229             # Not_analyzed field 'status' contains the EXACT term 'ACTIVE'
2230             { status => 'ACTIVE' }
2231             { status => { '=' => 'ACTIVE' }}
2232             { status => { match => 'ACTIVE' }}
2233              
2234             # Same as above but with extra parameters:
2235             { title => {
2236             match => {
2237             query => 'Perl is GREAT',
2238             boost => 2.0,
2239             operator => 'and',
2240             analyzer => 'default',
2241             fuzziness => 0.5,
2242             fuzzy_rewrite => 'constant_score_default',
2243             lenient => 1,
2244             max_expansions => 100,
2245             minimum_should_match => 2,
2246             prefix_length => 2,
2247             }
2248             }}
2249              
2250             Operators C<< <> >>, C<!=> and C<not_match> are synonyms for each other and
2251             just wrap the operator in a C<not> clause.
2252              
2253             See L<Match Query|http://www.elasticsearch.org/guide/reference/query-dsl/match-query.html>
2254              
2255             =head3 == | -phrase | -not_phrase
2256              
2257             These operators look for a complete phrase.
2258              
2259             For instance, given the text
2260              
2261             The quick brown fox jumped over the lazy dog.
2262              
2263             # matches
2264             { content => { '==' => 'Quick Brown' }}
2265              
2266             # doesn't match
2267             { content => { '==' => 'Brown Quick' }}
2268             { content => { '==' => 'Quick Fox' }}
2269              
2270             The C<slop> parameter can be used to allow the phrase to match words in the
2271             same order, but further apart:
2272              
2273             # with other parameters
2274             { content => {
2275             phrase => {
2276             query => 'Quick Fox',
2277             slop => 3,
2278             analyzer => 'default'
2279             boost => 1,
2280             lenient => 1,
2281             }}
2282              
2283             See L<Match Query|http://www.elasticsearch.org/guide/reference/query-dsl/match-query.html>
2284              
2285             =head3 Multi-field -match | -not_match
2286              
2287             To run a C<match> | C<=>, C<phrase> or C<phrase_prefix> query against
2288             multiple fields, you can use the C<-match> unary operator:
2289              
2290             {
2291             -match => {
2292             query => "Quick Fox",
2293             type => 'boolean',
2294             fields => ['content','title'],
2295              
2296             use_dis_max => 1,
2297             tie_breaker => 0.7,
2298              
2299             boost => 2.0,
2300             operator => 'and',
2301             analyzer => 'default',
2302             fuzziness => 0.5,
2303             fuzzy_rewrite => 'constant_score_default',
2304             lenient => 1,
2305             max_expansions => 100,
2306             minimum_should_match => 2,
2307             prefix_length => 2,
2308             }
2309             }
2310              
2311             The C<type> parameter can be C<boolean> (equivalent of C<match> | C<=>)
2312             which is the default, C<phrase> or C<phrase_prefix>.
2313              
2314             See L<Multi-match Query|http://www.elasticsearch.org/guide/reference/query-dsl/multi-match-query.html>.
2315              
2316             =head3 -term | -terms | -not_term | -not_terms
2317              
2318             The C<term>/C<terms> operators are provided for completeness. You
2319             should almost always use the C<match>/C<=> operator instead.
2320              
2321             There are only two use cases:
2322              
2323             =over
2324              
2325             =item *
2326              
2327             To find the exact (ie not analyzed) term 'foo' in an analyzed field:
2328              
2329             { title => { term => 'foo' }}
2330              
2331             =item *
2332              
2333             To match a list of possible terms, where more than 1 value must match:
2334              
2335             # match 2 or more of these tags
2336             { tags => {
2337             terms => {
2338             value => ['perl','python','php'],
2339             minimum_match => 2,
2340             boost => 1,
2341             }
2342             }}
2343              
2344             The above can also be achieved with the L</"-bool"> operator.
2345              
2346             =back
2347              
2348             C<term> and C<terms> are synonyms, as are C<not_term> and C<not_terms>.
2349              
2350             =head2 EQUALITY (FILTERS)
2351              
2352             =head3 = | -term | -terms | <> | != | -not_term | -not_terms
2353              
2354             These operators result in C<term> or C<terms> filters, which look for
2355             fields which contain exactly the terms specified:
2356              
2357             # Field foo has the term 'bar':
2358             { foo => 'bar' }
2359             { foo => { '=' => 'bar' }}
2360             { foo => { 'term' => 'bar' }}
2361              
2362             # Field foo has the term 'bar' or 'baz'
2363             { foo => ['bar','baz'] }
2364             { foo => { '=' => ['bar','baz'] }}
2365             { foo => { 'term' => ['bar','baz'] }}
2366              
2367             C<< <> >> and C<!=> are synonyms:
2368              
2369             # Field foo does not contain the term 'bar':
2370             { foo => { '!=' => 'bar' }}
2371             { foo => { '<>' => 'bar' }}
2372              
2373             # Field foo contains neither 'bar' nor 'baz'
2374             { foo => { '!=' => ['bar','baz'] }}
2375             { foo => { '<>' => ['bar','baz'] }}
2376              
2377             The C<terms> filter can take an C<execution> parameter which affects how the
2378             filter of multiple terms is executed and cached.
2379              
2380             For instance:
2381              
2382             { foo => {
2383             -terms => {
2384             value => ['foo','bar'],
2385             execution => 'bool'
2386             }
2387             }}
2388              
2389             See L<Term Filter|http://www.elasticsearch.org/guide/reference/query-dsl/term-filter.html>
2390             and L<Terms Filter|http://www.elasticsearch.org/guide/reference/query-dsl/terms-filter.html>
2391              
2392             =head1 RANGES
2393              
2394             =head2 lt | gt | lte | gte | < | <= | >= | > | -range | -not_range
2395              
2396             These operators imply a range query or filter, which can be numeric or
2397             alphabetical.
2398              
2399             # Field foo contains terms between 'alpha' and 'beta'
2400             { foo => {
2401             'gte' => 'alpha',
2402             'lte' => 'beta'
2403             }}
2404              
2405             # Field foo contains numbers between 10 and 20
2406             { foo => {
2407             'gte' => '10',
2408             'lte' => '20'
2409             }}
2410              
2411             # boost a range *** query only ***
2412             { foo => {
2413             range => {
2414             gt => 5,
2415             gte => 5,
2416             lt => 10,
2417             lte => 10,
2418             boost => 2.0
2419             }
2420             }}
2421              
2422             For queries, C<< < >> is a synonym for C<lt>, C<< > >> for C<gt> etc.
2423              
2424             See L<Range Query|http://www.elasticsearch.org/guide/reference/query-dsl/range-query.html>
2425              
2426             B<Note>: for filter clauses, the C<gt>,C<gte>,C<lt> and C<lte> operators
2427             imply a C<range> filter, while the C<< < >>, C<< <= >>, C<< > >> and C<< >= >>
2428             operators imply a C<numeric_range> filter.
2429              
2430             B<< This does not mean that you should use the C<numeric_range> version
2431             for any field which contains numbers! >>
2432              
2433             The C<numeric_range> filter should be used for numbers/datetimes which
2434             have many distinct values, eg C<ID> or C<last_modified>. If you have a numeric
2435             field with few distinct values, eg C<number_of_fingers> then it is better
2436             to use a C<range> filter.
2437              
2438             See L<Range Filter|http://www.elasticsearch.org/guide/reference/query-dsl/range-filter.html>
2439             and L<Numeric Range Filter|http://www.elasticsearch.org/guide/reference/query-dsl/numeric-range-filter.html>.
2440              
2441             =head1 MISSING OR NULL VALUES
2442              
2443             *** Filter context only ***
2444              
2445             =head2 -missing | -exists
2446              
2447             You can use a C<missing> or C<exists> filter to select only docs where a
2448             particular field exists and has a value, or is undefined or has no value:
2449              
2450             # Field 'foo' has a value:
2451             { foo => { exists => 1 }}
2452             { foo => { missing => 0 }}
2453             { -exists => 'foo' }
2454              
2455             # Field 'foo' is undefined or has no value:
2456             { foo => { missing => 1 }}
2457             { foo => { exists => 0 }}
2458             { -missing => 'foo' }
2459             { foo => undef }
2460              
2461             The C<missing> filter also supports the C<null_value> and C<existence>
2462             parameters:
2463              
2464             {
2465             foo => {
2466             missing => {
2467             null_value => 1,
2468             existence => 1,
2469             }
2470             }
2471             }
2472              
2473             OR
2474              
2475             { -missing => {
2476             field => 'foo',
2477             null_value => 1,
2478             existence => 1,
2479             }}
2480              
2481             See L<Missing Filter|http://www.elasticsearch.org/guide/reference/query-dsl/missing-filter.html>
2482             and L<Exists Filter|http://www.elasticsearch.org/guide/reference/query-dsl/exists-filter.html>
2483              
2484             =head1 FULL TEXT SEARCH
2485              
2486             *** Query context only ***
2487              
2488             For most full text search queries, the C<match> queries are what you
2489             want. These analyze the search terms, and look for documents that
2490             contain one or more of those terms. (See L</"EQUALITY (QUERIES)">).
2491              
2492             =head2 -qs | -query_string | -not_qs | -not_query_string
2493              
2494             However, there is a more advanced query string syntax
2495             (see L<Lucene Query Parser Syntax|http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html>)
2496             which understands search terms like:
2497              
2498             perl AND python tag:recent "this exact phrase" -apple
2499              
2500             It is useful for "power" users, but has the disadvantage that, if
2501             the syntax is incorrect, ES throws an error. You can use
2502             L<ElasticSearch::QueryParser> to fix any syntax errors.
2503              
2504             # find docs whose 'title' field matches 'this AND that'
2505             { title => { qs => 'this AND that' }}
2506             { title => { query_string => 'this AND that' }}
2507              
2508             # With other parameters
2509             { title => {
2510             field => {
2511             query => 'this that ',
2512             default_operator => 'AND',
2513             analyzer => 'default',
2514             allow_leading_wildcard => 0,
2515             lowercase_expanded_terms => 1,
2516             enable_position_increments => 1,
2517             fuzzy_min_sim => 0.5,
2518             fuzzy_prefix_length => 2,
2519             fuzzy_rewrite => 'constant_score_default',
2520             fuzzy_max_expansions => 1024,
2521             lenient => 1,
2522             phrase_slop => 10,
2523             boost => 2,
2524             analyze_wildcard => 1,
2525             auto_generate_phrase_queries => 0,
2526             rewrite => 'constant_score_default',
2527             minimum_should_match => 3,
2528             quote_analyzer => 'standard',
2529             quote_field_suffix => '.unstemmed'
2530             }
2531             }}
2532              
2533             The unary form C<-qs> or C<-query_string> can be used when matching
2534             against multiple fields:
2535              
2536             { -qs => {
2537             query => 'this AND that ',
2538             fields => ['title','content'],
2539             default_operator => 'AND',
2540             analyzer => 'default',
2541             allow_leading_wildcard => 0,
2542             lowercase_expanded_terms => 1,
2543             enable_position_increments => 1,
2544             fuzzy_min_sim => 0.5,
2545             fuzzy_prefix_length => 2,
2546             fuzzy_rewrite => 'constant_score_default',
2547             fuzzy_max_expansions => 1024,
2548             lenient => 1,
2549             phrase_slop => 10,
2550             boost => 2,
2551             analyze_wildcard => 1,
2552             auto_generate_phrase_queries => 0,
2553             use_dis_max => 1,
2554             tie_breaker => 0.7,
2555             minimum_should_match => 3,
2556             quote_analyzer => 'standard',
2557             quote_field_suffix => '.unstemmed'
2558             }}
2559              
2560             See L<Query-string Query|http://www.elasticsearch.org/guide/reference/query-dsl/query-string-query.html>
2561              
2562              
2563             =head2 -mlt | -not_mlt
2564              
2565             An C<mlt> or C<more_like_this> query finds documents that are "like" the
2566             specified text, where "like" means that it contains some or all of the
2567             specified terms.
2568              
2569             # Field foo is like "brown cow"
2570             { foo => { mlt => "brown cow" }}
2571              
2572             # With other paramters:
2573             { foo => {
2574             mlt => {
2575             like_text => 'brown cow',
2576             percent_terms_to_match => 0.3,
2577             min_term_freq => 2,
2578             max_query_terms => 25,
2579             stop_words => ['the','and'],
2580             min_doc_freq => 5,
2581             max_doc_freq => 1000,
2582             min_word_len => 0,
2583             max_word_len => 20,
2584             boost_terms => 2,
2585             boost => 2.0,
2586             analyzer => 'default'
2587             }
2588             }}
2589              
2590             # multi fields
2591             { -mlt => {
2592             like_text => 'brown cow',
2593             fields => ['title','content']
2594             percent_terms_to_match => 0.3,
2595             min_term_freq => 2,
2596             max_query_terms => 25,
2597             stop_words => ['the','and'],
2598             min_doc_freq => 5,
2599             max_doc_freq => 1000,
2600             min_word_len => 0,
2601             max_word_len => 20,
2602             boost_terms => 2,
2603             boost => 2.0,
2604             analyzer => 'default'
2605             }}
2606              
2607             See L<MLT Field Query|http://www.elasticsearch.org/guide/reference/query-dsl/mlt-field-query.html>
2608             and L<MLT Query|http://www.elasticsearch.org/guide/reference/query-dsl/mlt-query.html>
2609              
2610              
2611             =head2 -flt | -not_flt
2612              
2613             An C<flt> or C<fuzzy_like_this> query fuzzifies all specified terms, then
2614             picks the best C<max_query_terms> differentiating terms. It is a combination
2615             of C<fuzzy> with C<more_like_this>.
2616              
2617             # Field foo is fuzzily similar to "brown cow"
2618             { foo => { flt => 'brown cow }}
2619              
2620             # With other parameters:
2621             { foo => {
2622             flt => {
2623             like_text => 'brown cow',
2624             ignore_tf => 0,
2625             max_query_terms => 10,
2626             min_similarity => 0.5,
2627             prefix_length => 3,
2628             boost => 2.0,
2629             analyzer => 'default'
2630             }
2631             }}
2632              
2633             # Multi-field
2634             flt => {
2635             like_text => 'brown cow',
2636             fields => ['title','content'],
2637             ignore_tf => 0,
2638             max_query_terms => 10,
2639             min_similarity => 0.5,
2640             prefix_length => 3,
2641             boost => 2.0,
2642             analyzer => 'default'
2643             }}
2644              
2645             See L<FLT Field Query|http://www.elasticsearch.org/guide/reference/query-dsl/flt-field-query.html>
2646             and L<FLT Query|http://www.elasticsearch.org/guide/reference/query-dsl/flt-query.html>
2647              
2648             =head1 PREFIX
2649              
2650             =head2 PREFIX (QUERIES)
2651              
2652             =head3 ^ | -phrase_prefix | -not_phrase_prefix
2653              
2654             These operators use the C<match_phrase_prefix> query.
2655              
2656             For C<analyzed> fields, it analyzes the search terms, and does a
2657             C<match_phrase> query, with a C<prefix> query on the last term.
2658             Think "auto-complete".
2659              
2660             For C<not_analyzed> fields, this behaves the same as the term-based
2661             C<prefix> query.
2662              
2663             For instance, given the phrase
2664             C<The quick brown fox jumped over the lazy dog>:
2665              
2666             # matches
2667             { content => { '^' => 'qui'}}
2668             { content => { '^' => 'quick br'}}
2669             { content => { 'phrase_prefix' => 'quick brown f'}}
2670              
2671             # doesn't match
2672             { content => { '^' => 'quick fo' }}
2673             { content => { 'phrase_prefix' => 'fox brow'}}
2674              
2675             With extra options
2676              
2677             { content => {
2678             phrase_prefix => {
2679             query => "Brown Fo",
2680             slop => 3,
2681             analyzer => 'default',
2682             boost => 3.0,
2683             max_expansions => 100,
2684             }
2685             }}
2686              
2687             See http://www.elasticsearch.org/guide/reference/query-dsl/match-query.html
2688              
2689             =head3 -prefix | -not_prefix
2690              
2691             The C<prefix> query is a term-based query - no analysis takes place,
2692             even on analyzed fields. Generally you should use C<^> instead.
2693              
2694             # Field 'lang' contains terms beginning with 'p'
2695             { lang => { prefix => 'p' }}
2696              
2697             # With extra options
2698             { lang => {
2699             'prefix' => {
2700             value => 'p',
2701             boost => 2,
2702             rewrite => 'constant_score_default',
2703              
2704             }
2705             }}
2706              
2707             See L<Prefix Query|http://www.elasticsearch.org/guide/reference/query-dsl/prefix-query.html>.
2708              
2709             =head2 PREFIX (FILTERS)
2710              
2711              
2712             =head3 ^ | -prefix | -not_prefix
2713              
2714             # Field foo contains a term which begins with 'bar'
2715             { foo => { '^' => 'bar' }}
2716             { foo => { 'prefix' => 'bar' }}
2717              
2718             # Field foo contains a term which begins with 'bar' or 'baz'
2719             { foo => { '^' => ['bar','baz'] }}
2720             { foo => { 'prefix' => ['bar','baz'] }}
2721              
2722             # Field foo contains a term which begins with neither 'bar' nor 'baz'
2723             { foo => { 'not_prefix' => ['bar','baz'] }}
2724              
2725             See L<Prefix Filter|http://www.elasticsearch.org/guide/reference/query-dsl/prefix-filter.html>
2726              
2727              
2728             =head1 WILDCARD AND FUZZY QUERIES
2729              
2730             *** Query context only ***
2731              
2732             =head2 * | -wildcard | -not_wildcard
2733              
2734             A C<wildcard> is a term-based query (no analysis is applied), which
2735             does shell globbing to find matching terms. In other words C<?>
2736             represents any single character, while C<*> represents zero or more
2737             characters.
2738              
2739             # Field foo matches 'f?ob*'
2740             { foo => { '*' => 'f?ob*' }}
2741             { foo => { 'wildcard' => 'f?ob*' }}
2742              
2743             # with a boost:
2744             { foo => {
2745             '*' => { value => 'f?ob*', boost => 2.0 }
2746             }}
2747             { foo => {
2748             'wildcard' => {
2749             value => 'f?ob*',
2750             boost => 2.0,
2751             rewrite => 'constant_score_default',
2752             }
2753             }}
2754              
2755             See L<Wildcard Query|http://www.elasticsearch.org/guide/reference/query-dsl/wildcard-query.html>
2756              
2757             =head2 -fuzzy | -not_fuzzy
2758              
2759             A C<fuzzy> query is a term-based query (ie no analysis is done)
2760             which looks for terms that are similar to the the provided terms,
2761             where similarity is based on the Levenshtein (edit distance) algorithm:
2762              
2763             # Field foo is similar to 'fonbaz'
2764             { foo => { fuzzy => 'fonbaz' }}
2765              
2766             # With other parameters:
2767             { foo => {
2768             fuzzy => {
2769             value => 'fonbaz',
2770             boost => 2.0,
2771             min_similarity => 0.2,
2772             max_expansions => 10,
2773             rewrite => 'constant_score_default',
2774             }
2775             }}
2776              
2777             Normally, you should rather use either the L</"EQUALITY"> queries with
2778             the C<fuzziness> parameter, or the L<-flt|/"-flt E<verbar> -not_flt"> queries.
2779              
2780             See L<Fuzzy Query|http://www.elasticsearch.org/guide/reference/query-dsl/fuzzy-query.html>.
2781              
2782             =head1 COMBINING QUERIES
2783              
2784             *** Query context only ***
2785              
2786             These constructs allow you to combine multiple queries.
2787              
2788             =head2 -dis_max | -dismax
2789              
2790             While a C<bool> query adds together the scores of the nested queries, a
2791             C<dis_max> query uses the highest score of any matching queries.
2792              
2793             # Run the two queries and use the best score
2794             { -dismax => [
2795             { foo => 'bar' },
2796             { foo => 'baz' }
2797             ] }
2798              
2799             # With other parameters
2800             { -dismax => {
2801             queries => [
2802             { foo => 'bar' },
2803             { foo => 'baz' }
2804             ],
2805             tie_breaker => 0.5,
2806             boost => 2.0
2807             ] }
2808              
2809             See L<DisMax Query|http://www.elasticsearch.org/guide/reference/query-dsl/dis-max-query.html>
2810              
2811             =head2 -bool
2812              
2813             Normally, there should be no need to use a C<bool> query directly, as these
2814             are autogenerated from eg C<-and>, C<-or> and C<-not> constructs. However,
2815             if you need to pass any of the other parameters to a C<bool> query, then
2816             you can do the following:
2817              
2818             {
2819             -bool => {
2820             must => [{ foo => 'bar' }],
2821             must_not => { status => 'inactive' },
2822             should => [
2823             { tag => 'perl' },
2824             { tag => 'python' },
2825             { tag => 'ruby' },
2826             ],
2827             minimum_number_should_match => 2,
2828             disable_coord => 1,
2829             boost => 2
2830             }
2831             }
2832              
2833             See L<Bool Query|http://www.elasticsearch.org/guide/reference/query-dsl/bool-query.html>
2834              
2835             =head2 -boosting
2836              
2837             The C<boosting> query can be used to "demote" results that match a given query.
2838             Unlike the C<must_not> clause of a C<bool> query, the query still matches,
2839             but the results are "less relevant".
2840              
2841             { -boosting => {
2842             positive => { title => 'apple pear' },
2843             negative => { title => 'apple computer' },
2844             negative_boost => 0.2
2845             }}
2846              
2847             See L<Boosting Query|http://www.elasticsearch.org/guide/reference/query-dsl/boosting-query.html>
2848              
2849             =head2 -custom_boost
2850              
2851             The C<custom_boost> query allows you to multiply the scores of another query
2852             by the specified boost factor. This is a bit different from a standard C<boost>,
2853             which is normalized.
2854              
2855             {
2856             -custom_boost => {
2857             query => { title => 'foo' },
2858             boost_factor => 3
2859             }
2860             }
2861              
2862             See L<Custom Boost Factor Query|http://www.elasticsearch.org/guide/reference/query-dsl/custom-boost-factor-query.html>.
2863              
2864             =head1 NESTED QUERIES/FILTERS
2865              
2866             Nested queries/filters allow you to run queries/filters on nested docs.
2867              
2868             Normally, a doc like this would not allow you to associate the name C<perl>
2869             with the number C<5>
2870              
2871             {
2872             title: "my title",
2873             tags: [
2874             { name: "perl", num: 5},
2875             { name: "python", num: 2}
2876             ]
2877             }
2878              
2879             However, if C<tags> is mapped as a C<nested> field, then you can run queries
2880             or filters on each sub-doc individually.
2881              
2882             See L<Nested Type|http://www.elasticsearch.org/guide/reference/mapping/nested-type.html>,
2883             L<Nested Query|http://www.elasticsearch.org/guide/reference/query-dsl/nested-query.html>
2884             and L<Nested Filter|http://www.elasticsearch.org/guide/reference/query-dsl/nested-filter.html>
2885              
2886             =head2 -nested (QUERY)
2887              
2888             {
2889             -nested => {
2890             path => 'tags',
2891             score_mode => 'avg',
2892             _scope => 'my_tags',
2893             query => {
2894             "tags.name" => 'perl',
2895             "tags.num" => { gt => 2 },
2896             }
2897             }
2898             }
2899              
2900             See L<Nested Query|http://www.elasticsearch.org/guide/reference/query-dsl/nested-query.html>
2901              
2902             =head2 -nested (FILTER)
2903              
2904             {
2905             -nested => {
2906             path => 'tags',
2907             score_mode => 'avg',
2908             _cache => 1,
2909             _name => 'my_filter',
2910             filter => {
2911             tags.name => 'perl',
2912             tags.num => { gt => 2},
2913             }
2914             }
2915             }
2916              
2917             See L<Nested Filter|http://www.elasticsearch.org/guide/reference/query-dsl/nested-filter.html>
2918              
2919             =head1 SCRIPTING
2920              
2921             ElasticSearch supports the use of scripts to customise query or filter
2922             behaviour. By default the query language is C<mvel> but javascript, groovy,
2923             python and native java scripts are also supported.
2924              
2925             See L<Scripting|http://www.elasticsearch.org/guide/reference/modules/scripting.html> for
2926             more on scripting.
2927              
2928             =head2 -custom_score
2929              
2930             *** Query context only ***
2931              
2932             The C<-custom_score> query allows you to customise the C<_score> or relevance
2933             (and thus the order) of docs returned from a query.
2934              
2935             {
2936             -custom_score => {
2937             query => { foo => 'bar' },
2938             lang => 'mvel',
2939             script => "_score * doc['my_numeric_field'].value / pow(param1, param2)"
2940             params => {
2941             param1 => 2,
2942             param2 => 3.1
2943             },
2944             }
2945             }
2946              
2947             See L<Custom Score Query|http://www.elasticsearch.org/guide/reference/query-dsl/custom-score-query.html>
2948              
2949             =head2 -custom_filters_score
2950              
2951             *** Query context only ***
2952              
2953             The C<-custom_filters_score> query allows you to boost documents that match
2954             a filter, either with a C<boost> parameter, or with a custom C<script>.
2955              
2956             This is a very powerful and efficient way to boost results which depend on
2957             matching unanalyzed fields, eg a C<tag> or a C<date>. Also, these filters
2958             can be cached.
2959              
2960             {
2961             -custom_filters_score => {
2962             query => { foo => 'bar' },
2963             score_mode => 'first|max|total|avg|min|multiply', # default 'first'
2964             max_boost => 10,
2965             filters => [
2966             {
2967             filter => { tag => 'perl' },
2968             boost => 2,
2969             },
2970             {
2971             filter => { tag => 'python' },
2972             script => '_score * my_boost',
2973             params => { my_boost => 2},
2974             lang => 'mvel'
2975             },
2976             ]
2977             }
2978             }
2979              
2980             See L<Custom Filters Score Query|http://www.elasticsearch.org/guide/reference/query-dsl/custom-filters-score-query.html>
2981              
2982             =head2 -script
2983              
2984             *** Filter context only ***
2985              
2986             The C<-script> filter allows you to use a script as a filter. Return a true
2987             value to indicate that the filter matches.
2988              
2989             # Filter docs whose field 'foo' is greater than 5
2990             { -script => "doc['foo'].value > 5 " }
2991              
2992             # With other params
2993             {
2994             -script => {
2995             script => "doc['foo'].value > minimum ",
2996             params => { minimum => 5 },
2997             lang => 'mvel'
2998             }
2999             }
3000              
3001             See L<Script Filter|http://www.elasticsearch.org/guide/reference/query-dsl/script-filter.html>
3002              
3003             =head1 PARENT/CHILD
3004              
3005             Documents stored in ElasticSearch can be configured to have parent/child
3006             relationships.
3007              
3008             See L<Parent Field|http://www.elasticsearch.org/guide/reference/mapping/parent-field.html>
3009             for more.
3010              
3011             =head2 -has_parent | -not_has_parent
3012              
3013             Find child documents that have a parent document which matches a query.
3014              
3015             # Find parent docs whose children of type 'comment' have the tag 'perl'
3016             {
3017             -has_parent => {
3018             type => 'comment',
3019             query => { tag => 'perl' },
3020             _scope => 'my_scope',
3021             boost => 1, # Query context only
3022             score_type => 'max' # Query context only
3023             }
3024             }
3025              
3026             See L<Has Parent Query|http://www.elasticsearch.org/guide/reference/query-dsl/has-parent-query.html>
3027             and See L<Has Parent Filter|http://www.elasticsearch.org/guide/reference/query-dsl/has-parent-filter.html>.
3028              
3029             =head2 -has_child | -not_has_child
3030              
3031             Find parent documents that have child documents which match a query.
3032              
3033             # Find parent docs whose children of type 'comment' have the tag 'perl'
3034             {
3035             -has_child => {
3036             type => 'comment',
3037             query => { tag => 'perl' },
3038             _scope => 'my_scope',
3039             boost => 1, # Query context only
3040             score_type => 'max' # Query context only
3041             }
3042             }
3043              
3044             See L<Has Child Query|http://www.elasticsearch.org/guide/reference/query-dsl/has-child-query.html>
3045             and See L<Has Child Filter|http://www.elasticsearch.org/guide/reference/query-dsl/has-child-filter.html>.
3046              
3047             =head2 -top_children
3048              
3049             *** Query context only ***
3050              
3051             The C<top_children> query runs a query against the child docs, and aggregates
3052             the scores to find the parent docs whose children best match.
3053              
3054             {
3055             -top_children => {
3056             type => 'blog_tag',
3057             query => { tag => 'perl' },
3058             score => 'max',
3059             factor => 5,
3060             incremental_factor => 2,
3061             _scope => 'my_scope'
3062             }
3063             }
3064              
3065             See L<Top Children Query|http://www.elasticsearch.org/guide/reference/query-dsl/top-children-query.html>
3066              
3067             =head1 GEO FILTERS
3068              
3069             For all the geo filters, the C<normalize> parameter defaults to C<true>, meaning
3070             that the longitude value will be normalized to C<-180> to C<180> and the
3071             latitude value to C<-90> to C<90>.
3072              
3073             =head2 -geo_distance | -not_geo_distance
3074              
3075             *** Filter context only ***
3076              
3077             The C<geo_distance> filter will find locations within a certain distance of
3078             a given point:
3079              
3080             {
3081             my_location => {
3082             -geo_distance => {
3083             location => { lat => 10, lon => 5 },
3084             distance => '5km',
3085             normalize => 1 | 0,
3086             optimize_bbox => memory | indexed | none,
3087             }
3088             }
3089             }
3090              
3091             See L<Geo Distance Filter|http://www.elasticsearch.org/guide/reference/query-dsl/geo-distance-filter.html>
3092              
3093             =head2 -geo_distance_range | -not_geo_distance_range
3094              
3095             *** Filter context only ***
3096              
3097             The C<geo_distance_range> filter is similar to the L<-geo_distance|/"-geo_distance | -not_geo_distance">
3098             filter, but expressed as a range:
3099              
3100             {
3101             my_location => {
3102             -geo_distance => {
3103             location => { lat => 10, lon => 5 },
3104             from => '5km',
3105             to => '10km',
3106             include_lower => 1 | 0,
3107             include_upper => 0 | 1
3108             normalize => 1 | 0,
3109             optimize_bbox => memory | indexed | none,
3110             }
3111             }
3112             }
3113              
3114             or instead of C<from>, C<to>, C<include_lower> and C<include_upper> you can
3115             use C<gt>, C<gte>, C<lt>, C<lte>.
3116              
3117             See L<Geo Distance Range Filter|http://www.elasticsearch.org/guide/reference/query-dsl/geo-distance-range-filter.html>
3118              
3119             =head2 -geo_bounding_box | -geo_bbox | -not_geo_bounding_box | -not_geo_bbox
3120              
3121             *** Filter context only ***
3122              
3123             The C<geo_bounding_box> filter finds points which lie within the given
3124             rectangle:
3125              
3126             {
3127             my_location => {
3128             -geo_bbox => {
3129             top_left => { lat => 9, lon => 4 },
3130             bottom_right => { lat => 10, lon => 5 },
3131             normalize => 1 | 0,
3132             type => memory | indexed
3133             }
3134             }
3135             }
3136              
3137             See L<Geo Bounding Box Filter|http://www.elasticsearch.org/guide/reference/query-dsl/geo-bounding-box-filter.html>
3138              
3139             =head2 -geo_polygon | -not_geo_polygon
3140              
3141             *** Filter context only ***
3142              
3143             The C<geo_polygon> filter is similar to the L<-geo_bounding_box|/"-geo_bounding_box | -geo_bbox | -not_geo_bounding_box | -not_geo_bbox">
3144             filter, except that it allows you to specify a polygon instead of a rectangle:
3145              
3146             {
3147             my_location => {
3148             -geo_polygon => [
3149             { lat => 40, lon => -70 },
3150             { lat => 30, lon => -80 },
3151             { lat => 20, lon => -90 },
3152             ]
3153             }
3154             }
3155              
3156             or:
3157              
3158             {
3159             my_location => {
3160             -geo_polygon => {
3161             points => [
3162             { lat => 40, lon => -70 },
3163             { lat => 30, lon => -80 },
3164             { lat => 20, lon => -90 },
3165             ],
3166             normalize => 1 | 0,
3167             }
3168             }
3169             }
3170              
3171             See L<Geo Polygon Filter|http://www.elasticsearch.org/guide/reference/query-dsl/geo-polygon-filter.html>
3172              
3173             =head1 INDEX/TYPE/ID
3174              
3175             =head2 -indices
3176              
3177             *** Query context only ***
3178              
3179             To run a different query depending on the index name, you can use the
3180             C<-indices> query:
3181              
3182             {
3183             -indices => {
3184             indices => 'one' | ['one','two],
3185             query => { status => 'active' },
3186             no_match_query => 'all' | 'none' | { another => query }
3187             }
3188             }
3189              
3190             The `no_match_query` will be run on any indices which don't appear in the
3191             specified list. It defaults to C<all>, but can be set to C<none> or to
3192             a full query.
3193              
3194             See L<Indices Query|http://www.elasticsearch.org/guide/reference/query-dsl/indices-query.html>.
3195              
3196             *** Filter context only ***
3197              
3198             To run a different filter depending on the index name, you can use the
3199             C<-indices> filter:
3200              
3201             {
3202             -indices => {
3203             indices => 'one' | ['one','two],
3204             filter => { status => 'active' },
3205             no_match_filter => 'all' | 'none' | { another => filter }
3206             }
3207             }
3208              
3209             The `no_match_filter` will be run on any indices which don't appear in the
3210             specified list. It defaults to C<all>, but can be set to C<none> or to
3211             a full filter.
3212              
3213             See L<Indices Filter|https://github.com/elasticsearch/elasticsearch/issues/1787>.
3214              
3215             =head2 -ids
3216              
3217             The C<_id> field is not indexed by default, and thus isn't
3218             available for normal queries or filters
3219              
3220             Returns docs with the matching C<_id> or C<_type>/C<_id> combination:
3221              
3222             # doc with ID 123
3223             { -ids => 123 }
3224              
3225             # docs with IDs 123 or 124
3226             { -ids => [123,124] }
3227              
3228             # docs of types 'blog' or 'comment' with IDs 123 or 124
3229             {
3230             -ids => {
3231             type => ['blog','comment'],
3232             values => [123,124]
3233              
3234             }
3235             }
3236              
3237             See L<IDs Query|http://www.elasticsearch.org/guide/reference/query-dsl/ids-query.html>
3238             abd L<IDs Filter|http://www.elasticsearch.org/guide/reference/query-dsl/ids-filter.html>
3239              
3240             =head2 -type
3241              
3242             *** Filter context only ***
3243              
3244             Filters docs with matching C<_type> fields.
3245              
3246             While the C<_type> field is indexed by default, ElasticSearch provides the
3247             C<type> filter which will work even if indexing of the C<_type> field is
3248             disabled.
3249              
3250             # Filter docs of type 'comment'
3251             { -type => 'comment' }
3252              
3253             # Filter docs of type 'comment' or 'blog'
3254             { -type => ['blog','comment' ]}
3255              
3256             See L<Type Filter|http://www.elasticsearch.org/guide/reference/query-dsl/type-filter.html>
3257              
3258              
3259             =head1 LIMIT
3260              
3261             *** Filter context only ***
3262              
3263             The C<limit> filter limits the number of documents (per shard) to execute on:
3264              
3265             {
3266             name => "Joe Bloggs",
3267             -filter => { -limit => 100 }
3268             }
3269              
3270             See L<Limit Filter|http://www.elasticsearch.org/guide/reference/query-dsl/limit-filter.html>
3271              
3272             =head1 NAMED FILTERS
3273              
3274             ElasticSearch allows you to name filters, in which each search result will
3275             include a C<matched_filters> array containing the names of all filters that
3276             matched.
3277              
3278             =head2 -name | -not_name
3279              
3280             *** Filter context only ***
3281              
3282             { -name => {
3283             popular => { user_rank => { 'gte' => 10 }},
3284             unpopular => { user_rank => { 'lt' => 10 }},
3285             }}
3286              
3287             Multiple filters are joined with an C<or> filter (as it doesn't make sense
3288             to join them with C<and>).
3289              
3290             See L<Named Filters|http://www.elasticsearch.org/guide/reference/api/search/named-filters.html>
3291             and L</"-and E<verbar> -or E<verbar> -not">.
3292              
3293             =head1 CACHING FILTERS
3294              
3295             Part of the performance boost that you get when using filters comes from the
3296             ability to cache the results of those filters. However, it doesn't make
3297             sense to cache all filters by default.
3298              
3299             =head2 -cache | -nocache
3300              
3301             *** Filter context only ***
3302              
3303             If you would like to override the default caching, then you can use
3304             C<-cache> or C<-nocache>:
3305              
3306             # Don't cache the term filter for 'status'
3307             {
3308             content => 'interesting post',
3309             -filter => {
3310             -nocache => { status => 'active' }
3311             }
3312             }
3313              
3314             # Do cache the numeric range filter:
3315             {
3316             content => 'interesting post',
3317             -filter => {
3318             -cache => { created => {'>' => '2010-01-01' } }
3319             }
3320             }
3321              
3322             See L<Query DSL|http://www.elasticsearch.org/guide/reference/query-dsl/> for more
3323             details about what is cached by default and what is not.
3324              
3325             =head2 -cache_key
3326              
3327             It is also possible to use a name to identify a cached filter. For instance:
3328              
3329             {
3330             -cache_key => {
3331             friends => { person_id => [1,2,3] },
3332             enemies => { person_id => [4,5,6] },
3333             }
3334             }
3335              
3336             In the above example, the two filters will be joined by an C<and> filter. The
3337             following example will have the two filters joined by an C<or> filter:
3338              
3339             {
3340             -cache_key => [
3341             friends => { person_id => [1,2,3] },
3342             enemies => { person_id => [4,5,6] },
3343             ]
3344             }
3345              
3346             See L<_cache_key|http://www.elasticsearch.org/guide/reference/query-dsl/index.html> for
3347             more details.
3348              
3349             =head1 RAW ELASTICSEARCH QUERY DSL
3350              
3351             Sometimes, instead of using the SearchBuilder syntax, you may want to revert
3352             to the raw Query DSL that ElasticSearch uses.
3353              
3354             You can do this by passing a reference to a HASH ref, for instance:
3355              
3356             $sb->query({
3357             foo => 1,
3358             -filter => \{ term => { bar => 2 }}
3359             })
3360              
3361             Would result in:
3362              
3363             {
3364             query => {
3365             filtered => {
3366             query => {
3367             match => { foo => 1 }
3368             },
3369             filter => {
3370             term => { bar => 2 }
3371             }
3372             }
3373             }
3374             }
3375              
3376             An example with OR'ed filters:
3377              
3378             $sb->filter([
3379             foo => 1,
3380             \{ term => { bar => 2 }}
3381             ])
3382              
3383             Would result in:
3384              
3385             {
3386             filter => {
3387             or => [
3388             { term => { foo => 1 }},
3389             { term => { bar => 2 }}
3390             ]
3391             }
3392             }
3393              
3394             An example with AND'ed filters:
3395              
3396             $sb->filter({
3397             -and => [
3398             foo => 1 ,
3399             \{ term => { bar => 2 }}
3400             ]
3401             })
3402              
3403             Would result in:
3404              
3405             {
3406             filter => {
3407             and => [
3408             { term => { foo => 1 }},
3409             { term => { bar => 2 }}
3410             ]
3411             }
3412             }
3413              
3414             Wherever a filter or query is expected, passing a reference to a HASH-ref is
3415             accepted.
3416              
3417             =cut
3418              
3419             =head1 ELASTICSEARCH CONCEPTS
3420              
3421             =head2 FILTERS VS QUERIES
3422              
3423             ElasticSearch supports filters and queries:
3424              
3425             =over
3426              
3427             =item *
3428              
3429             A filter just answers the question: "Does this field match? Yes/No", eg:
3430              
3431             =over
3432              
3433             =item *
3434              
3435             Does this document have the tag C<"beta">?
3436              
3437             =item *
3438              
3439             Was this document published in 2011?
3440              
3441             =back
3442              
3443             =item *
3444              
3445             A query is used to calculate relevance (
3446             known in ElasticSearch as C<_score>):
3447              
3448             =over
3449              
3450             =item *
3451              
3452             Give me all documents that include the keywords C<"Foo"> and C<"Bar"> and
3453             rank them in order of relevance.
3454              
3455             =item *
3456              
3457             Give me all documents whose C<tag> field contains C<"perl"> or C<"ruby">
3458             and rank documents that contain BOTH tags more highly.
3459              
3460             =back
3461              
3462             =back
3463              
3464             Filters are lighter and faster, and the results can often be cached, but they
3465             don't contribute to the C<_score> in any way.
3466              
3467             Typically, most of your clauses will be filters, and just a few will be queries.
3468              
3469             =head2 TERMS VS TEXT
3470              
3471             All data is stored in ElasticSearch as a C<term>, which is an exact value.
3472             The term C<"Foo"> is not the same as C<"foo">.
3473              
3474             While this is useful for fields that have discreet values (eg C<"active">,
3475             C<"inactive">), it is not sufficient to support full text search.
3476              
3477             ElasticSearch has to I<analyze> text to convert it into terms. This applies
3478             both to the text that the stored document contains, and to the text that the
3479             user tries to search on.
3480              
3481             The default analyzer will:
3482              
3483             =over
3484              
3485             =item *
3486              
3487             split the text on (most) punctuation and remove that punctuation
3488              
3489             =item *
3490              
3491             lowercase each word
3492              
3493             =item *
3494              
3495             remove English stopwords
3496              
3497             =back
3498              
3499             For instance, C<"The 2 GREATEST widgets are foo-bar and fizz_buzz"> would result
3500             in the terms C< [2,'greatest','widgets','foo','bar','fizz_buzz']>.
3501              
3502             It is important that the same analyzer is used both for the stored text
3503             and for the search terms, otherwise the resulting terms may be different,
3504             and the query won't succeed.
3505              
3506             For instance, a C<term> query for C<GREATEST> wouldn't work, but C<greatest>
3507             would work. However, a C<match> query for C<GREATEST> would work, because
3508             the search text would be analyzed to produce the same terms that are stored
3509             in the index.
3510              
3511             See L<Analysis|http://www.elasticsearch.org/guide/reference/index-modules/analysis/>
3512             for the list of supported analyzers.
3513              
3514             =head2 C<match> QUERIES
3515              
3516             ElasticSearch has a family of DWIM queries called C<match> queries.
3517              
3518             Their action depends upon how the field has been defined. If a field is
3519             C<analyzed> (the default for string fields) then the C<match> queries analyze
3520             the search terms before doing the search:
3521              
3522             # Convert "Perl is GREAT" to the terms 'perl','great' and search
3523             # the 'content' field for those terms
3524              
3525             { match: { content: "Perl is GREAT" }}
3526              
3527             If a field is C<not_analyzed>, then it treats the search terms as a single
3528             term:
3529              
3530             # Find all docs where the 'status' field contains EXACTLY the term 'ACTIVE'
3531             { match: { status: "ACTIVE" }}
3532              
3533             Filters, on the other hand, don't have full text queries - filters operate on
3534             simple terms instead.
3535              
3536             See L<Match Query|http://www.elasticsearch.org/guide/reference/query-dsl/match-query.html>
3537             for more about match queries.
3538              
3539             =cut
3540              
3541             =head1 AUTHOR
3542              
3543             Clinton Gormley, C<< <drtech at cpan.org> >>
3544              
3545             =head1 BUGS
3546              
3547             If you have any suggestions for improvements, or find any bugs, please report
3548             them to L<https://github.com/clintongormley/ElasticSearch-SearchBuilder/issues>.
3549             I will be notified, and then you'll automatically be notified of progress on
3550             your bug as I make changes.
3551              
3552             =head1 TODO
3553              
3554             Add support for C<span> queries.
3555              
3556             =head1 SUPPORT
3557              
3558             You can find documentation for this module with the perldoc command.
3559              
3560             perldoc ElasticSearch::SearchBuilder
3561              
3562              
3563             You can also look for information at: L<http://www.elasticsearch.org>
3564              
3565              
3566             =head1 ACKNOWLEDGEMENTS
3567              
3568             Thanks to SQL::Abstract for providing the inspiration and some of the internals.
3569              
3570             =head1 LICENSE AND COPYRIGHT
3571              
3572             Copyright 2011 Clinton Gormley.
3573              
3574             This program is free software; you can redistribute it and/or modify it
3575             under the terms of either: the GNU General Public License as published
3576             by the Free Software Foundation; or the Artistic License.
3577              
3578             See L<http://dev.perl.org/licenses/> for more information.
3579              
3580              
3581             =cut
3582