source: repository/lib/Metabrik/Client/Elasticsearch/Query.pm

Last change on this file was 918:aa1dbe000fdc, checked in by GomoR <gomor@…>, 2 weeks ago
  • bugfix: client::elasticsearch: correctly display usage for query Command on invalid call
  • bugfix: client::elasticsearch::query: use size Attribute when querying
  • bugfix: lookup::iplocation: shorten error message when IPv4 has no location found in from_ip Command
File size: 15.8 KB
Line 
1#
2# $Id$
3#
4# client::elasticsearch::query Brik
5#
6package Metabrik::Client::Elasticsearch::Query;
7use strict;
8use warnings;
9
10use base qw(Metabrik::Client::Elasticsearch);
11
12sub brik_properties {
13   return {
14      revision => '$Revision$',
15      tags => [ qw(unstable) ],
16      author => 'GomoR <GomoR[at]metabrik.org>',
17      license => 'http://opensource.org/licenses/BSD-3-Clause',
18      attributes => {
19         nodes => [ qw(node_list) ], # Inherited
20         index => [ qw(index) ],     # Inherited
21         type => [ qw(type) ],       # Inherited
22         from => [ qw(number) ],     # Inherited
23         size => [ qw(count) ],      # Inherited
24         client => [ qw(INTERNAL) ],
25      },
26      attributes_default => {
27         index => '*',
28         type => '*',
29      },
30      commands => {
31         create_client => [ ],
32         reset_client => [ ],
33         query => [ qw(query index|OPTIONAL type|OPTIONAL) ],
34         get_query_result_total => [ qw($query_result|OPTIONAL) ],
35         get_query_result_hits => [ qw($query_result|OPTIONAL) ],
36         get_query_result_timed_out => [ qw($query_result|OPTIONAL) ],
37         get_query_result_took => [ qw($query_result|OPTIONAL) ],
38         term => [ qw(kv index|OPTIONAL type|OPTIONAL) ],
39         unique_term => [ qw(unique kv index|OPTIONAL type|OPTIONAL) ],
40         unique_values => [ qw(field index|OPTIONAL type|OPTIONAL) ],
41         wildcard => [ qw(kv index|OPTIONAL type|OPTIONAL) ],
42         range => [ qw(kv_from kv_to index|OPTIONAL type|OPTIONAL) ],
43         top => [ qw(kv_count index|OPTIONAL type|OPTIONAL) ],
44         top_match => [ qw(kv_count kv_match index|OPTIONAL type|OPTIONAL) ],
45         match => [ qw(kv index|OPTIONAL type|OPTIONAL) ],
46         match_phrase => [ qw(kv index|OPTIONAL type|OPTIONAL) ],
47         from_json_file => [ qw(json_file index|OPTIONAL type|OPTIONAL) ],
48         from_dump_file => [ qw(dump_file index|OPTIONAL type|OPTIONAL) ],
49      },
50      require_modules => {
51         'Metabrik::File::Json' => [ ],
52         'Metabrik::File::Dump' => [ ],
53      },
54   };
55}
56
57sub create_client {
58   my $self = shift;
59
60   my $ce = $self->client;
61   if (! defined($ce)) {
62      $ce = $self->open or return;
63      $self->client($ce);
64   }
65
66   return $ce;
67}
68
69sub reset_client {
70   my $self = shift;
71
72   my $ce = $self->client;
73   if (defined($ce)) {
74      $self->client(undef);
75   }
76
77   return 1;
78}
79
80sub get_query_result_total {
81   my $self = shift;
82   my ($query_result) = @_;
83
84   my $run = $self->context->do('$RUN');
85   $query_result ||= $run;
86   $self->brik_help_run_undef_arg('get_query_result_total', $query_result) or return;
87   $self->brik_help_run_invalid_arg('get_query_result_total', $query_result, 'HASH') or return;
88
89   if (! exists($query_result->{hits})) {
90      return $self->log->error("get_query_result_total: invalid query result, no hits found");
91   }
92   if (! exists($query_result->{hits}{total})) {
93      return $self->log->error("get_query_result_total: invalid query result, no total found");
94   }
95
96   return $query_result->{hits}{total};
97}
98
99sub get_query_result_hits {
100   my $self = shift;
101   my ($query_result) = @_;
102
103   my $run = $self->context->do('$RUN');
104   $query_result ||= $run;
105   $self->brik_help_run_undef_arg('get_query_result_hits', $query_result) or return;
106   $self->brik_help_run_invalid_arg('get_query_result_hits', $query_result, 'HASH') or return;
107
108   if (! exists($query_result->{hits})) {
109      return $self->log->error("get_query_result_hits: invalid query result, no hits found");
110   }
111   if (! exists($query_result->{hits}{hits})) {
112      return $self->log->error("get_query_result_hits: invalid query result, no hits in hits found");
113   }
114
115   return $query_result->{hits}{hits};
116}
117
118sub get_query_result_timed_out {
119   my $self = shift;
120   my ($query_result) = @_;
121
122   my $run = $self->context->do('$RUN');
123   $query_result ||= $run;
124   $self->brik_help_run_undef_arg('get_query_result_timed_out', $query_result) or return;
125   $self->brik_help_run_invalid_arg('get_query_result_timed_out', $query_result, 'HASH')
126      or return;
127
128   if (! exists($query_result->{timed_out})) {
129      return $self->log->error("get_query_result_timed_out: invalid query result, ".
130         "no timed_out found");
131   }
132
133   return $query_result->{timed_out} ? 1 : 0;
134}
135
136sub get_query_result_took {
137   my $self = shift;
138   my ($query_result) = @_;
139
140   my $run = $self->context->do('$RUN');
141   $query_result ||= $run;
142   $self->brik_help_run_undef_arg('get_query_result_took', $query_result) or return;
143   $self->brik_help_run_invalid_arg('get_query_result_took', $query_result, 'HASH')
144      or return;
145
146   if (! exists($query_result->{took})) {
147      return $self->log->error("get_query_result_took: invalid query result, no took found");
148   }
149
150   return $query_result->{took};
151}
152
153sub query {
154   my $self = shift;
155   my ($q, $index, $type) = @_;
156
157   $index ||= '*';
158   $type ||= '*';
159   $self->brik_help_run_undef_arg('query', $q) or return;
160
161   my $ce = $self->create_client or return;
162
163   my $r = $self->SUPER::query($q, $index, $type) or return;
164   if (defined($r)) {
165      if (exists($r->{hits}{total})) {
166         return $r;
167      }
168      else {
169         return $self->log->error("query: failed with [$r]");
170      }
171   }
172
173   return $self->log->error("query: failed");
174}
175
176#
177# run client::elasticsearch::query term domain=example.com index1-*,index2-*
178#
179sub term {
180   my $self = shift;
181   my ($kv, $index, $type) = @_;
182
183   $index ||= $self->index;
184   $type ||= $self->type;
185   $self->brik_help_run_undef_arg('term', $kv) or return;
186   $self->brik_help_set_undef_arg('term', $index) or return;
187   $self->brik_help_set_undef_arg('term', $type) or return;
188
189   if ($kv !~ /^\S+?=.+$/) {
190      return $self->log->error("term: kv must be in the form 'key=value'");
191   }
192   my ($key, $value) = split('=', $kv);
193
194   $self->debug && $self->log->debug("term: key[$key] value[$value]");
195
196   # Optimized version on ES 5.0.0
197   my $q = {
198      size => $self->size,
199      query => {
200         bool => {
201            must => { term => { $key => $value } },
202         },
203      },
204   };
205
206   $self->log->verbose("term: keys [$key] value [$value] index [$index] type [$type]");
207
208   return $self->query($q, $index, $type);
209}
210
211#
212# run client::elasticsearch::query unique_term ip domain=example.com index1-*,index2-*
213#
214sub unique_term {
215   my $self = shift;
216   my ($unique, $kv, $index, $type) = @_;
217
218   $index ||= $self->index;
219   $type ||= $self->type;
220   $self->brik_help_run_undef_arg('unique_term', $unique) or return;
221   $self->brik_help_run_undef_arg('unique_term', $kv) or return;
222   $self->brik_help_set_undef_arg('unique_term', $index) or return;
223   $self->brik_help_set_undef_arg('unique_term', $type) or return;
224
225   if ($kv !~ m{^.+?=.+$}) {
226      return $self->log->error("unique_term: kv [$kv] must be in the form ".
227         "'key=value'");
228   }
229   my ($key, $value) = split('=', $kv);
230
231   $self->debug && $self->log->debug("unique_term: key[$key] value[$value]");
232
233   # Optimized version on ES 5.0.0
234   my $q = {
235      size => 0,
236      query => {
237         bool => {
238            must => { term => { $key => $value } },
239         },
240      },
241      aggs => {
242         1 => {
243            cardinality => {
244               field => $unique,
245               precision_threshold => 40000,
246            },
247         },
248      },
249   };
250
251   $self->log->verbose("unique_term: unique [$unique] keys [$key] value [$value] ".
252      "index [$index] type [$type]");
253
254   return $self->query($q, $index, $type);
255}
256
257#
258#
259#
260sub unique_values {
261   my $self = shift;
262   my ($field, $index, $type) = @_;
263
264   $index ||= $self->index;
265   $type ||= $self->type;
266   $self->brik_help_run_undef_arg('unique_values', $field) or return;
267   $self->brik_help_set_undef_arg('unique_values', $index) or return;
268   $self->brik_help_set_undef_arg('unique_values', $type) or return;
269
270   my $size = $self->size * 10;
271
272   # Will return 10*100000=1_000_000 unique values.
273   my $q = {
274      aggs => {
275         1 => {
276            terms => {
277               field => $field,
278               include => { num_partitions => 10, partition => 0 },
279               size => $size,
280            },
281         },
282      },
283      size => 0,
284   };
285
286   $self->log->verbose("unique_values: unique [$field] index [$index] type [$type]");
287
288   return $self->query($q, $index, $type);
289}
290
291sub wildcard {
292   my $self = shift;
293   my ($kv, $index, $type) = @_;
294
295   $index ||= $self->index;
296   $type ||= $self->type;
297   $self->brik_help_run_undef_arg('wildcard', $kv) or return;
298   $self->brik_help_set_undef_arg('wildcard', $index) or return;
299   $self->brik_help_set_undef_arg('wildcard', $type) or return;
300
301   if ($kv !~ /^\S+?=.+$/) {
302      return $self->log->error("wildcard: kv must be in the form 'key=value'");
303   }
304   my ($key, $value) = split('=', $kv);
305
306   my $q = {
307      size => $self->size,
308      query => {
309         #constant_score => {  # Does not like constant_score
310            #filter => {
311               wildcard => {
312                  $key => $value,
313               },
314            #},
315         #},
316      },
317   };
318
319   $self->log->verbose("wildcard: keys [$key] value [$value] index [$index] type [$type]");
320
321   return $self->query($q, $index, $type);
322}
323
324#
325# run client::elasticsearch::query range ip_range.from=192.168.255.36 ip_range.to=192.168.255.36
326#
327sub range {
328   my $self = shift;
329   my ($kv_from, $kv_to, $index, $type) = @_;
330
331   $index ||= $self->index;
332   $type ||= $self->type;
333   $self->brik_help_run_undef_arg('range', $kv_from) or return;
334   $self->brik_help_run_undef_arg('range', $kv_to) or return;
335   $self->brik_help_set_undef_arg('range', $index) or return;
336   $self->brik_help_set_undef_arg('range', $type) or return;
337
338   if ($kv_from !~ /^\S+?=.+$/) {
339      return $self->log->error("range: kv_from [$kv_from] must be in the form 'key=value'");
340   }
341   if ($kv_to !~ /^\S+?=.+$/) {
342      return $self->log->error("range: kv_to [$kv_to] must be in the form 'key=value'");
343   }
344   my ($key_from, $value_from) = split('=', $kv_from);
345   my ($key_to, $value_to) = split('=', $kv_to);
346
347   #
348   # http://stackoverflow.com/questions/40519806/no-query-registered-for-filtered
349   # https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-filtered-query.html
350   # Compatible with ES 5.0
351   #
352   my $q = {
353      size => $self->size,
354      query => {
355         bool => {
356            must => [
357               { range => { $key_to => { gte => $value_to } } },
358               { range => { $key_from => { lte => $value_from } } },
359            ],
360         },
361      },
362   };
363
364   return $self->query($q, $index, $type);
365}
366
367#
368# run client::elasticsearch::query top name=10 users-*
369#
370sub top {
371   my $self = shift;
372   my ($kv_count, $index, $type) = @_;
373
374   $index ||= $self->index;
375   $type ||= $self->type;
376   $self->brik_help_run_undef_arg('top', $kv_count) or return;
377   $self->brik_help_set_undef_arg('top', $index) or return;
378   $self->brik_help_set_undef_arg('top', $type) or return;
379
380   if ($kv_count !~ /^\S+=\d+$/) {
381      return $self->log->error("top: kv_count [$kv_count] must be in the form 'key=value'");
382   }
383   my ($key_count, $value_count) = split('=', $kv_count);
384
385   my $q = {
386      aggs => {
387         top_values => {
388            terms => {
389               field => $key_count,
390               size => int($value_count),
391               order => { _count => 'desc' },
392            },
393         },
394      },
395   };
396
397   $self->log->verbose("top: key [$key_count] value [$value_count] index [$index] type [$type]");
398
399   return $self->query($q, $index, $type);
400}
401
402sub match_phrase {
403   my $self = shift;
404   my ($kv, $index, $type) = @_;
405
406   $index ||= $self->index;
407   $type ||= $self->type;
408   $self->brik_help_run_undef_arg('match_phrase', $kv) or return;
409   $self->brik_help_set_undef_arg('match_phrase', $index) or return;
410   $self->brik_help_set_undef_arg('match_phrase', $type) or return;
411
412   if ($kv !~ /^\S+?=.+$/) {
413      return $self->log->error("match_phrase: kv must be in the form 'key=value'");
414   }
415   my ($key, $value) = split('=', $kv);
416
417   $self->debug && $self->log->debug("match_phrase: key[$key] value[$value]");
418
419   my $q = {
420      size => $self->size,
421      query => {
422         match_phrase => {
423            $key => $value,
424         },
425      },
426   };
427
428   return $self->query($q, $index, $type);
429}
430
431sub match {
432   my $self = shift;
433   my ($kv, $index, $type) = @_;
434
435   $index ||= $self->index;
436   $type ||= $self->type;
437   $self->brik_help_run_undef_arg('match', $kv) or return;
438   $self->brik_help_set_undef_arg('match', $index) or return;
439   $self->brik_help_set_undef_arg('match', $type) or return;
440
441   if ($kv !~ /^\S+?=.+$/) {
442      return $self->log->error("match: kv must be in the form 'key=value'");
443   }
444   my ($key, $value) = split('=', $kv);
445
446   $self->debug && $self->log->debug("match: key[$key] value[$value]");
447
448   my $q = {
449      size => $self->size,
450      query => {
451         match => {
452            $key => $value,
453         },
454      },
455   };
456
457   return $self->query($q, $index, $type);
458}
459
460#
461# run client::elasticsearch::query top_match domain=10 host=*www*
462#
463sub top_match {
464   my $self = shift;
465   my ($kv_count, $kv_match, $index, $type) = @_;
466
467   $index ||= $self->index;
468   $type ||= $self->type;
469   $self->brik_help_run_undef_arg('top_match', $kv_count) or return;
470   $self->brik_help_run_undef_arg('top_match', $kv_match) or return;
471   $self->brik_help_set_undef_arg('top_match', $index) or return;
472   $self->brik_help_set_undef_arg('top_match', $type) or return;
473
474   if ($kv_count !~ /^\S+?=.+$/) {
475      return $self->log->error("top_match: kv_count [$kv_count] must be in the form 'key=value'");
476   }
477   if ($kv_match !~ /^\S+?=.+$/) {
478      return $self->log->error("top_match: kv_match [$kv_match] must be in the form 'key=value'");
479   }
480   my ($key_count, $value_count) = split('=', $kv_count);
481   my ($key_match, $value_match) = split('=', $kv_match);
482
483   my $q = {
484      size => $self->size,
485      query => {
486         #constant_score => {   # Does not like constant_score
487            #filter => {
488               match => {
489                  $key_match => $value_match,
490               },
491            #},
492         #},
493      },
494      aggs => {
495         top_values => {
496            terms => {
497               field => $key_count,
498               size => int($value_count),
499            },
500         },
501      },
502   };
503
504   return $self->query($q, $index, $type);
505}
506
507sub from_json_file {
508   my $self = shift;
509   my ($file, $index, $type) = @_;
510
511   $index ||= $self->index;
512   $type ||= $self->type;
513   $self->brik_help_run_undef_arg('from_json_file', $file) or return;
514   $self->brik_help_run_file_not_found('from_json_file', $file) or return;
515   $self->brik_help_set_undef_arg('from_json_file', $index) or return;
516   $self->brik_help_set_undef_arg('from_json_file', $type) or return;
517
518   my $fj = Metabrik::File::Json->new_from_brik_init($self) or return;
519   my $q = $fj->read($file) or return;
520
521   if (defined($q) && length($q)) {
522      return $self->query($q, $index, $type);
523   }
524
525   return $self->log->error("from_json_file: nothing to read from this file [$file]");
526}
527
528sub from_dump_file {
529   my $self = shift;
530   my ($file, $index, $type) = @_;
531
532   $index ||= $self->index;
533   $type ||= $self->type;
534   $self->brik_help_run_undef_arg('from_dump_file', $file) or return;
535   $self->brik_help_run_file_not_found('from_dump_file', $file) or return;
536   $self->brik_help_set_undef_arg('from_dump_file', $index) or return;
537   $self->brik_help_set_undef_arg('from_dump_file', $type) or return;
538
539   my $fd = Metabrik::File::Dump->new_from_brik_init($self) or return;
540   my $q = $fd->read($file) or return;
541
542   my $first = $q->[0];
543   if (defined($first)) {
544      return $self->query($first, $index, $type);
545   }
546
547   return $self->log->error("from_dump_file: nothing to read from this file [$file]");
548}
549
5501;
551
552__END__
553
554=head1 NAME
555
556Metabrik::Client::Elasticsearch::Query - client::elasticsearch::query Brik
557
558=head1 COPYRIGHT AND LICENSE
559
560Copyright (c) 2014-2017, Patrice E<lt>GomoRE<gt> Auffret
561
562You may distribute this module under the terms of The BSD 3-Clause License.
563See LICENSE file in the source distribution archive.
564
565=head1 AUTHOR
566
567Patrice E<lt>GomoRE<gt> Auffret
568
569=cut
Note: See TracBrowser for help on using the repository browser.