File Coverage

blib/lib/InfluxDB.pm
Criterion Covered Total %
statement 41 191 21.4
branch 6 66 9.0
condition 2 3 66.6
subroutine 13 40 32.5
pod 25 29 86.2
total 87 329 26.4


line stmt bran cond sub pod time code
1             package InfluxDB;
2              
3 4     4   14932 use strict;
  4         3  
  4         91  
4 4     4   12 use warnings;
  4         3  
  4         74  
5 4     4   65 use 5.010_000;
  4         13  
6              
7             our $VERSION = '1.005';
8              
9             use Class::Accessor::Lite (
10 4         26 new => 0,
11             ro => [qw(host port username database ssl json)],
12 4     4   1479 );
  4         3141  
13              
14 4     4   285 use Carp;
  4         5  
  4         197  
15 4     4   1599 use Data::Validator;
  4         83940  
  4         102  
16 4     4   25 use Mouse::Util::TypeConstraints;
  4         5  
  4         11  
17 4     4   1819 use Furl;
  4         64878  
  4         98  
18 4     4   1543 use URI::Escape;
  4         3692  
  4         241  
19 4     4   593 use JSON 2;
  4         8097  
  4         22  
20              
21             enum 'TimePrecision' => qw(s m u);
22              
23             subtype 'JSONBool' => as 'ScalarRef';
24             coerce 'JSONBool'
25             => from 'Bool' => via { $_ ? \1 : \0 }
26             => from 'Object' => via { JSON::is_bool($_) ? ($_ == 1 ? \1 : \0) : \0 }
27             ;
28              
29             sub new {
30 6     6 1 2426 state $rule = Data::Validator->new(
31             host => { isa => 'Str' },
32             port => { isa => 'Int', default => 8086 },
33             username => { isa => 'Str', optional => 1 },
34             password => { isa => 'Str', optional => 1 },
35             database => { isa => 'Str' },
36             ssl => { isa => 'Bool', default => 0 },
37              
38             timeout => { isa => 'Int', default => 120 },
39             debug => { isa => 'Bool', optional => 1 },
40             )->with('NoRestricted')->with('Method');
41             # Mouse::Util::apply_all_role leaks memory when takes 2 or more extensions.
42             # so apply for each extension.
43 6         14589 my($class, $args) = $rule->validate(@_);
44              
45 6 50       466 if (delete $args->{debug}) {
46 0         0 $ENV{IX_DEBUG} = 1;
47             }
48              
49             my $self = bless {
50             ua => Furl->new(
51             agent => join('/', __PACKAGE__, $VERSION),
52             timeout => $args->{timeout},
53 6         27 ),
54             json => JSON->new,
55             status => {},
56             %$args
57             }, $class;
58              
59 6         261 return $self;
60             }
61              
62             sub debugf {
63 2 50   2 0 6 return unless $ENV{IX_DEBUG};
64 0         0 print STDERR "[DEBUG] ",sprintf(shift @_, @_),"\n";
65             }
66              
67             sub status {
68 0     0 1 0 my($self, $res) = @_;
69              
70 0 0       0 if ($res) {
71             $self->{status} = {
72 0         0 code => $res->code,
73             message => $res->message,
74             status_line => $res->status_line,
75             content => $res->content,
76             };
77 0         0 debugf("content: %s", $res->content);
78             }
79              
80 0         0 return $self->{status};
81             }
82              
83             sub errstr {
84 0     0 1 0 my($self) = @_;
85 0         0 my $errstr = "";
86              
87 0 0       0 if (substr($self->{status}{code}, 0, 2) ne "20") {
88             $errstr = join("\n",
89             $self->{status}{status_line},
90             $self->{status}{content},
91 0         0 );
92             }
93              
94 0         0 return $errstr;
95             }
96              
97             sub as_hash {
98 0     0 1 0 my(undef, $result) = @_;
99 0         0 my $h;
100              
101 0         0 for my $r (@{ $result }) {
  0         0  
102 0         0 my $series = $r->{name};
103 0         0 my @columns = @{ $r->{columns} };
  0         0  
104              
105 0         0 for my $p (@{ $r->{points} }) {
  0         0  
106 0         0 my %ph;
107 0         0 @ph{ @columns } = @$p;
108 0         0 push @{ $h->{$series} }, \%ph;
  0         0  
109             }
110             }
111              
112 0         0 return $h;
113             }
114              
115             sub switch_database {
116 0     0 1 0 state $rule = Data::Validator->new(
117             database => { isa => 'Str' },
118             )->with('Method');
119 0         0 my($self, $args) = $rule->validate(@_);
120              
121 0         0 $self->{database} = $args->{database};
122              
123 0         0 return 1;
124             }
125              
126             sub switch_user {
127 0     0 1 0 state $rule = Data::Validator->new(
128             username => { isa => 'Str' },
129             password => { isa => 'Str' },
130             )->with('Method');
131 0         0 my($self, $args) = $rule->validate(@_);
132              
133 0         0 $self->{username} = $args->{username};
134 0         0 $self->{password} = $args->{password};
135              
136 0         0 return 1;
137             }
138              
139             ### database #############################################################
140             sub create_database {
141 0     0 1 0 state $rule = Data::Validator->new(
142             database => { isa => 'Str' },
143             )->with('Method');
144 0         0 my($self, $args) = $rule->validate(@_);
145              
146 0         0 my $url = $self->_build_url(
147             path => '/db',
148             );
149              
150             my $res = $self->{ua}->post($url, [], $self->json->encode({
151             name => $args->{database},
152 0         0 }));
153 0         0 $self->status($res);
154              
155 0 0       0 return $res->is_success ? 1 : ();
156             }
157              
158             sub list_database {
159 0     0 1 0 my $self = shift;
160              
161 0         0 my $url = $self->_build_url(
162             path => '/db',
163             );
164              
165 0         0 my $res = $self->{ua}->get($url);
166 0         0 $self->status($res);
167              
168 0 0       0 return $res->is_success ? $self->json->decode($res->content) : ();
169             }
170              
171             sub delete_database {
172 0     0 1 0 state $rule = Data::Validator->new(
173             database => { isa => 'Str' },
174             )->with('Method');
175 0         0 my($self, $args) = $rule->validate(@_);
176              
177             my $url = $self->_build_url(
178             path => '/db/'. $args->{database},
179 0         0 );
180              
181 0         0 my $res = $self->{ua}->delete($url);
182 0         0 $self->status($res);
183              
184 0 0       0 return $res->is_success ? 1 : ();
185             }
186              
187             ### series ###############################################################
188             ## hmmm v0.5.1 (latest version) returns empty response
189             ## https://github.com/FGRibreau/influxdb-cli/issues/8
190             sub list_series {
191 0     0 1 0 my $self = shift;
192 0         0 return $self->query(q => "list series");
193             }
194              
195             ### points ###############################################################
196             sub write_points {
197 0     0 1 0 state $rule = Data::Validator->new(
198             data => { isa => 'ArrayRef|HashRef' },
199             time_precision => { isa => 'TimePrecision', optional => 1 },
200             )->with('Method');
201 0         0 my($self, $args) = $rule->validate(@_);
202              
203 0 0       0 my $data = ref($args->{data}) eq 'ARRAY' ? $args->{data} : [$args->{data}];
204 0         0 $data = $self->json->encode($data);
205 0         0 debugf("data: %s", $data);
206              
207             my $url = $self->_build_url(
208             path => '/db/' . $self->database . '/series',
209             qs => {
210             time_precision => exists $args->{time_precision} ? $args->{time_precision} : undef,
211             },
212 0 0       0 );
213              
214 0         0 my $res = $self->{ua}->post($url, [], $data);
215 0         0 $self->status($res);
216              
217 0 0       0 return $res->is_success ? 1 : ();
218             }
219              
220             sub delete_points {
221 0     0 1 0 state $rule = Data::Validator->new(
222             name => { isa => 'Str' },
223             )->with('Method');
224 0         0 my($self, $args) = $rule->validate(@_);
225              
226             my $url = $self->_build_url(
227             path => '/db/' . $self->database . '/series/' . $args->{name},
228 0         0 qs => {
229             %$args,
230             },
231             );
232              
233 0         0 my $res = $self->{ua}->delete($url);
234 0         0 $self->status($res);
235              
236 0 0       0 return $res->is_success ? 1 : ();
237             }
238              
239             sub create_scheduled_deletes {
240 0     0 0 0 croak "Not implemented in InfluxDB v0.5.1";
241             # state $rule = Data::Validator->new(
242             # )->with('Method');
243             # my($self, $args) = $rule->validate(@_);
244             }
245              
246             sub list_scheduled_deletes {
247 0     0 0 0 croak "Not implemented in InfluxDB v0.5.1";
248             # state $rule = Data::Validator->new(
249             # )->with('Method');
250             # my($self, $args) = $rule->validate(@_);
251             }
252              
253             sub delete_scheduled_deletes {
254 0     0 0 0 croak "Not implemented in InfluxDB v0.5.1";
255             # state $rule = Data::Validator->new(
256             # )->with('Method');
257             # my($self, $args) = $rule->validate(@_);
258             }
259              
260             sub query {
261 0     0 1 0 state $rule = Data::Validator->new(
262             q => { isa => 'Str' },
263             time_precision => { isa => 'TimePrecision', optional => 1 },
264             chunked => { isa => 'Bool', default => 0 },
265             # order => { isa => 'Str', optional => 1 }, # not implemented?
266             )->with('Method');
267 0         0 my($self, $args) = $rule->validate(@_);
268              
269             my $url = $self->_build_url(
270             path => '/db/' . $self->database . '/series',
271             qs => {
272             q => $args->{q},
273             time_precision => (exists $args->{time_precision} ? $args->{time_precision} : undef),
274             chunked => ($args->{chunked} ? 'true' : 'false'),
275 0 0       0 order => (exists $args->{order} ? $args->{order} : undef),
    0          
    0          
276             },
277             );
278              
279 0         0 my $res = $self->{ua}->get($url);
280 0         0 $self->status($res);
281              
282 0 0       0 if ($res->is_success) {
283 0         0 my $result;
284 0 0       0 if ($args->{chunked}) {
285 0         0 $result = [$self->json->incr_parse($res->content)];
286             } else {
287 0         0 $result = $self->json->decode($res->content);
288             }
289 0         0 return $result;
290             } else {
291 0         0 return;
292             }
293             }
294              
295             ### Continuous Queries ###################################################
296             sub create_continuous_query {
297 0     0 1 0 state $rule = Data::Validator->new(
298             q => { isa => 'Str' },
299             name => { isa => 'Str' },
300             )->with('Method');
301 0         0 my($self, $args) = $rule->validate(@_);
302              
303 0         0 return $self->query(q => "$args->{q} into $args->{name}");
304             }
305              
306             sub list_continuous_queries {
307 0     0 1 0 my $self = shift;
308 0         0 return $self->query(q => "list continuous queries");
309             }
310              
311             sub drop_continuous_query {
312 0     0 1 0 state $rule = Data::Validator->new(
313             id => { isa => 'Str' },
314             )->with('Method');
315 0         0 my($self, $args) = $rule->validate(@_);
316              
317 0         0 return $self->query(q => "drop continuous query $args->{id}");
318             }
319              
320             ### user #################################################################
321             sub create_database_user {
322 0     0 1 0 state $rule = Data::Validator->new(
323             name => { isa => 'Str' },
324             password => { isa => 'Str' },
325             )->with('Method');
326 0         0 my($self, $args) = $rule->validate(@_);
327              
328 0         0 my $url = $self->_build_url(
329             path => '/db/' . $self->database . '/users',
330             );
331              
332             my $res = $self->{ua}->post($url, [], $self->json->encode({
333             name => $args->{name},
334             password => $args->{password},
335 0         0 }));
336 0         0 $self->status($res);
337              
338 0 0       0 return $res->is_success ? 1 : ();
339             }
340              
341             sub delete_database_user {
342 0     0 1 0 state $rule = Data::Validator->new(
343             name => { isa => 'Str' },
344             )->with('Method');
345 0         0 my($self, $args) = $rule->validate(@_);
346              
347             my $url = $self->_build_url(
348             path => '/db/' . $self->database . '/users/' . $args->{name},
349 0         0 );
350              
351 0         0 my $res = $self->{ua}->delete($url);
352 0         0 $self->status($res);
353              
354 0 0       0 return $res->is_success ? 1 : ();
355             }
356              
357             sub update_database_user {
358 0     0 1 0 state $rule = Data::Validator->new(
359             name => { isa => 'Str' },
360             password => { isa => 'Str', optional => 1 },
361             admin => { isa => 'JSONBool', optional => 1 },
362             )->with('Method');
363 0         0 my($self, $args) = $rule->validate(@_);
364              
365             my $url = $self->_build_url(
366             path => '/db/' . $self->database . '/users/' . $args->{name},
367 0         0 );
368              
369             my $res = $self->{ua}->post($url, [], $self->json->encode({
370             exists $args->{password} ? (password => $args->{password}) : (),
371 0 0       0 exists $args->{admin} ? (admin => $args->{admin}) : (),
    0          
372             }));
373 0         0 $self->status($res);
374              
375 0 0       0 return $res->is_success ? 1 : ();
376             }
377              
378             sub list_database_users {
379 0     0 1 0 my $self = shift;
380              
381 0         0 my $url = $self->_build_url(
382             path => '/db/' . $self->database . '/users',
383             );
384              
385 0         0 my $res = $self->{ua}->get($url);
386 0         0 $self->status($res);
387              
388 0 0       0 return $res->is_success ? $self->json->decode($res->content) : ();
389             }
390              
391             sub show_database_user {
392 0     0 1 0 state $rule = Data::Validator->new(
393             name => { isa => 'Str' },
394             )->with('Method');
395 0         0 my($self, $args) = $rule->validate(@_);
396              
397             my $url = $self->_build_url(
398             path => '/db/' . $self->database . '/users/' . $args->{name},
399 0         0 );
400              
401 0         0 my $res = $self->{ua}->get($url);
402 0         0 $self->status($res);
403              
404 0 0       0 return $res->is_success ? $self->json->decode($res->content) : ();
405             }
406              
407             sub create_cluster_admin {
408 0     0 1 0 state $rule = Data::Validator->new(
409             name => { isa => 'Str' },
410             password => { isa => 'Str' },
411             )->with('Method');
412 0         0 my($self, $args) = $rule->validate(@_);
413              
414 0         0 my $url = $self->_build_url(
415             path => '/cluster_admins',
416             );
417              
418             my $res = $self->{ua}->post($url, [], $self->json->encode({
419             name => $args->{name},
420             password => $args->{password},
421 0         0 }));
422 0         0 $self->status($res);
423              
424 0 0       0 return $res->is_success ? 1 : ();
425             }
426              
427             sub delete_cluster_admin {
428 0     0 1 0 state $rule = Data::Validator->new(
429             name => { isa => 'Str' },
430             )->with('Method');
431 0         0 my($self, $args) = $rule->validate(@_);
432              
433             my $url = $self->_build_url(
434             path => '/cluster_admins/' . $args->{name},
435 0         0 );
436              
437 0         0 my $res = $self->{ua}->delete($url);
438 0         0 $self->status($res);
439              
440 0 0       0 return $res->is_success ? 1 : ();
441             }
442              
443             sub update_cluster_admin {
444 0     0 1 0 state $rule = Data::Validator->new(
445             name => { isa => 'Str' },
446             password => { isa => 'Str' },
447             )->with('Method');
448 0         0 my($self, $args) = $rule->validate(@_);
449              
450             my $url = $self->_build_url(
451             path => '/cluster_admins/' . $args->{name},
452 0         0 );
453              
454             my $res = $self->{ua}->post($url, [], $self->json->encode({
455 0 0       0 exists $args->{password} ? (password => $args->{password}) : (),
456             }));
457 0         0 $self->status($res);
458              
459 0 0       0 return $res->is_success ? 1 : ();
460             }
461              
462             sub list_cluster_admins {
463 0     0 1 0 my $self = shift;
464              
465 0         0 my $url = $self->_build_url(
466             path => '/cluster_admins',
467             );
468              
469 0         0 my $res = $self->{ua}->get($url);
470 0         0 $self->status($res);
471              
472 0 0       0 return $res->is_success ? $self->json->decode($res->content) : ();
473             }
474              
475             # sub show_cluster_admin {
476             # state $rule = Data::Validator->new(
477             # name => { isa => 'Str' },
478             # )->with('Method');
479             # my($self, $args) = $rule->validate(@_);
480              
481             # my $url = $self->_build_url(
482             # path => '/cluster_admins/' . $args->{name},
483             # );
484              
485             # my $res = $self->{ua}->get($url);
486             # $self->status($res);
487              
488             # return $res->is_success ? $self->json->decode($res->content) : ();
489             # }
490              
491             ### utils ################################################################
492             sub _build_url {
493 2     2   11 state $rule = Data::Validator->new(
494             path => { isa => 'Str' },
495             qs => { isa => 'HashRef', optional => 1 },
496             )->with('Method');
497 2         345 my($self, $args) = $rule->validate(@_);
498              
499             my $url = sprintf("%s://%s%s:%d%s",
500             ($self->ssl ? 'https' : 'http'),
501             ($self->username and $self->{password})
502             ? sprintf('%s:%s@', $self->username, $self->{password})
503             : '',
504             $self->host,
505             $self->port,
506             $args->{path},
507 2 50 66     64 );
    100          
508 2 50       52 if (exists $args->{qs}) {
509 0         0 my @qs;
510 0         0 for my $k (keys %{ $args->{qs} }) {
  0         0  
511 0 0       0 next unless defined $args->{qs}{$k};
512 0         0 push @qs, join('=', uri_escape($k), uri_escape($args->{qs}{$k}));
513             }
514 0 0       0 $url .= '?' . join('&', @qs) if @qs;
515             }
516              
517 2         3 debugf("url: %s", $url);
518 2         4 return $url
519             }
520              
521             1;
522              
523             __END__