File Coverage

lib/DBD/Neo4p.pm
Criterion Covered Total %
statement 33 297 11.1
branch 0 110 0.0
condition 0 25 0.0
subroutine 12 37 32.4
pod 0 1 0.0
total 45 470 9.5


line stmt bran cond sub pod time code
1 1     1   92354 use v5.10.1;
  1         12  
2             package DBD::Neo4p;
3 1     1   6 use strict;
  1         2  
  1         30  
4 1     1   9 use warnings;
  1         2  
  1         33  
5 1     1   527 use REST::Neo4p 0.3030;
  1         88814  
  1         32  
6 1     1   11 use JSON;
  1         2  
  1         4  
7             require DBI;
8 1     1   112 no warnings qw/once/;
  1         3  
  1         39  
9              
10             BEGIN {
11 1     1   2072 $DBD::Neo4p::VERSION = '0.2000';
12             }
13              
14             our $err = 0; # holds error code for DBI::err
15             our $errstr = ''; # holds error string for DBI::errstr
16             our $drh = undef; # holds driver handle once initialised
17             our $prefix = 'neo';
18              
19             sub driver($$){
20 0 0   0 0   return $drh if $drh;
21 0           my($sClass, $rhAttr) = @_;
22 0           $sClass .= '::dr';
23              
24             # install methods if nec.
25              
26 0           DBD::Neo4p::db->install_method('neo_neo4j_version');
27              
28 0           $drh = DBI::_new_drh($sClass,
29             {
30             Name => $sClass,
31             Version => $DBD::Neo4p::VERSION,
32             Err => \$DBD::Neo4p::err,
33             Errstr => \$DBD::Neo4p::errstr,
34             State => \$DBD::Neo4p::sqlstate,
35             Attribution => 'DBD::Neo4p by Mark A. Jensen'
36             }
37             );
38 0           return $drh;
39             }
40              
41             package # hide from PAUSE
42             DBD::Neo4p::dr;
43             $DBD::Neo4p::dr::imp_data_size = 0;
44              
45             sub connect($$;$$$) {
46 0     0     my($drh, $sDbName, $sUsr, $sAuth, $rhAttr)= @_;
47              
48             #1. create database-handle
49 0           my ($outer, $dbh) = DBI::_new_dbh($drh, {
50             Name => $sDbName,
51             USER => $sUsr,
52             CURRENT_USER => $sUsr,
53             });
54 0           local $REST::Neo4p::HANDLE;
55 0           $dbh->STORE("${prefix}_Handle", REST::Neo4p->create_and_set_handle);
56             # default attributes
57 0           $dbh->STORE("${prefix}_ResponseAsObjects",0);
58              
59             #2. Parse extra strings in DSN(key1=val1;key2=val2;...)
60 0           foreach my $sItem (split(/;/, $sDbName)) {
61 0           my ($key, $value) = $sItem =~ /(.*?)=(.*)/;
62 0 0         return $drh->set_err($DBI::stderr, "Can't parse DSN part '$sItem'")
63             unless defined $value;
64 0 0         $key = "${prefix}_$key" unless $key =~ /^${prefix}_/;
65 0           $dbh->STORE($key, $value);
66             }
67 0           $DB::single=1;
68 0   0       my $db = delete $rhAttr->{"${prefix}_database"} || delete $rhAttr->{"${prefix}_db"} || $dbh->{neo_db};
69 0   0       my $host = $dbh->FETCH("${prefix}_host") || 'localhost';
70 0   0       my $port = $dbh->FETCH("${prefix}_port") || 7474;
71 0   0       my $protocol = $dbh->FETCH("${prefix}_protocol") || 'http';
72 0   0       my $user = delete $rhAttr->{Username} || $dbh->FETCH("${prefix}_user") || $sUsr;
73 0   0       my $pass = delete $rhAttr->{Password} || $dbh->FETCH("${prefix}_pass") || $sAuth;
74 0 0         if (my $ssl_opts = delete $rhAttr->{SSL_OPTS}) {
75 0 0         if (REST::Neo4p->agent->isa('LWP::UserAgent')) {
76 0           while (my ($k,$v) = each %$ssl_opts) {
77 0           REST::Neo4p->agent->ssl_opts($k => $v);
78             }
79             }
80             }
81             # use db=://: or host=;port=
82             # db attribute trumps
83              
84 0 0         if ($db) {
85 0           ($protocol, $host, $port) = $db =~ m|^(https?)?(?:://)?([^:]+):?([0-9]*)$|;
86 0   0       $protocol //= 'http';
87 0 0 0       return $drh->set_err($DBI::stderr, "DB host and/or port not specified correctly") unless ($host && $port);
88             }
89              
90             # real connect...
91              
92 0           $db = "$protocol://$host:$port";
93 0           eval {
94 0           REST::Neo4p->connect($db,$user,$pass);
95             };
96 0 0         if (my $e = Exception::Class->caught()) {
97             return
98 0 0         ref $e ? $drh->set_err($DBI::stderr, "Can't connect to $sDbName: ".ref($e)." : ".$e->message.' ('.$e->code.')') :
99             $drh->set_err($DBI::stderr, $e);
100             };
101              
102 0           foreach my $sKey (keys %$rhAttr) {
103 0           $dbh->STORE($sKey, $rhAttr->{$sKey});
104             }
105 0           $dbh->STORE(Active => 1);
106 0           $dbh->STORE(AutoCommit => 1);
107 0           $dbh->{"${prefix}_agent"} = REST::Neo4p->agent;
108              
109 0           return $outer;
110             }
111              
112             sub data_sources ($;$) {
113 0     0     my($drh, $rhAttr) = @_;
114 0           return;
115             }
116              
117       0     sub disconnect_all($) { }
118              
119             package #hide from PAUSE
120             DBD::Neo4p::db;
121             $DBD::Neo4p::db::imp_data_size = 0;
122              
123             sub prepare {
124 0     0     my($dbh, $sStmt, $rhAttr) = @_;
125             #1. Create blank sth
126 0           my ($outer, $sth) = DBI::_new_sth($dbh, { Statement => $sStmt, });
127 0 0         return $sth unless($sth);
128              
129             # cypher query parameters are given as tokens surrounded by curly braces:
130             # crude count:
131 0           my @parms = $sStmt =~ /\{\s*([^}[:space:]]*)\s*\}/g;
132 0           $sth->STORE('NUM_OF_PARAMS', scalar @parms);
133 0           $sth->{"${prefix}_param_names"} = \@parms;
134 0           $sth->{"${prefix}_param_values"} = [];
135 0           return $outer;
136             }
137              
138             sub begin_work {
139 0     0     my ($dbh) = @_;
140 0           local $REST::Neo4p::HANDLE;
141 0           REST::Neo4p->set_handle($dbh->{"${prefix}_Handle"});
142 0 0         unless ($dbh->{AutoCommit}) {
143 0           $drh->set_err($DBI::stderr, "begin_work not effective, AutoCommit already off");
144 0           return;
145             }
146 0           eval {
147 0           REST::Neo4p->begin_work;
148             };
149 0 0         if ( my $e = REST::Neo4p::VersionMismatchException->caught()) {
    0          
150 0 0         warn("Your neo4j server does not support transactions via REST API") if $dbh->FETCH('Warn');
151 0           return;
152             }
153             elsif ($e = Exception::Class->caught()) {
154             return
155 0 0         ref $e ? $drh->set_err($DBI::stderr, "Can't begin transaction: ".ref($e)." : ".$e->message.' ('.$e->code.')') :
156             $drh->set_err($DBI::stderr, $e);
157             };
158 0           $dbh->STORE('AutoCommit',0);
159 0           return 1;
160             }
161              
162             sub commit ($) {
163 0     0     my($dbh) = @_;
164 0 0         if ($dbh->FETCH('AutoCommit')) {
165 0 0         warn("Commit ineffective while AutoCommit is on") if $dbh->FETCH('Warn');
166 0           return;
167             }
168             else {
169 0           local $REST::Neo4p::HANDLE;
170 0           REST::Neo4p->set_handle($dbh->{"${prefix}_Handle"});
171 0           eval {
172 0           REST::Neo4p->commit;
173             };
174 0 0         if ( my $e = REST::Neo4p::VersionMismatchException->caught()) {
    0          
175 0 0         warn("Your neo4j server does not support REST transactions") if $dbh->FETCH('Warn');
176 0           return;
177             }
178             elsif ($e = Exception::Class->caught()) {
179             return
180 0 0         ref $e ? $drh->set_err($DBI::stderr, "Can't commit: ".ref($e)." : ".$e->message.' ('.$e->code.')') :
181             $drh->set_err($DBI::stderr, $e);
182             };
183 0           return 1;
184             }
185             }
186              
187             sub rollback ($) {
188 0     0     my($dbh) = @_;
189 0 0         if ($dbh->FETCH('AutoCommit')) {
190 0 0         warn("Rollback ineffective while AutoCommit is on") if $dbh->FETCH('Warn');
191 0           return;
192             }
193             else {
194 0           local $REST::Neo4p::HANDLE;
195 0           REST::Neo4p->set_handle($dbh->{"${prefix}_Handle"});
196 0           eval {
197 0           REST::Neo4p->rollback;
198             };
199 0 0         if ( my $e = REST::Neo4p::VersionMismatchException->caught()) {
    0          
200 0 0         warn("Your neo4j server does not support REST transactions") if $dbh->FETCH('Warn');
201 0           return;
202             }
203             elsif ($e = Exception::Class->caught()) {
204             return
205 0 0         ref $e ? $drh->set_err($DBI::stderr, "Can't rollback: ".ref($e)." : ".$e->message.' ('.$e->code.')') :
206             $drh->set_err($DBI::stderr, $e);
207             };
208 0           return 1;
209             }
210             }
211              
212             sub ping {
213 0     0     my $dbh = shift;
214 0 0         my $s = ($dbh->neo_neo4j_version =~ /^[3-9]\.0/ ? 'match (a) return a limit 1' :
215             'return 1');
216 0 0         my $sth = $dbh->prepare($s) or return 0;
217 0 0         $sth->execute or return 0;
218 0           $sth->finish;
219 0           return 1;
220             }
221              
222             # neo4j metadata -- needs thinking
223             # v2.0 : http://docs.neo4j.org/chunked/2.0.0-M06/rest-api-cypher.html#rest-api-retrieve-query-metadata
224              
225             sub neo_neo4j_version {
226 0     0     my $dbh = shift;
227 0           return $dbh->{"${prefix}_agent"}->{_actions}{neo4j_version};
228             }
229              
230              
231             # table_info is a nop
232              
233             sub table_info ($) {
234 0     0     my($dbh) = @_;
235             # -->> Change
236 0           my ($raTables, $raName) = (undef, undef);
237             # <<-- Change
238 0 0         return undef unless $raTables;
239             # 2. create DBD::Sponge driver
240 0           my $dbh2 = $dbh->{'_sponge_driver'};
241 0 0         if (!$dbh2) {
242 0           $dbh2 = $dbh->{'_sponge_driver'} = DBI->connect("DBI:Sponge:");
243 0 0         if (!$dbh2) {
244 0           $dbh->DBI::set_err( 1, $DBI::errstr);
245 0           return undef;
246 0           $DBI::errstr .= ''; #Just for IGNORE warning
247             }
248             }
249             #3. assign table info to the DBD::Sponge driver
250 0           my $sth = $dbh2->prepare("TABLE_INFO",
251             { 'rows' => $raTables, 'NAMES' => $raName });
252 0 0         if (!$sth) {
253 0           $dbh->DBI::set_err(1, $dbh2->errstr());
254             }
255 0           return $sth;
256             }
257              
258             sub type_info_all ($) {
259 0     0     my ($dbh) = @_;
260 0           return [];
261             }
262              
263             sub disconnect ($) {
264 0     0     my ($dbh) = @_;
265 0           eval {
266 0           REST::Neo4p->disconnect_handle($dbh->{"${prefix}_Handle"});
267             };
268 0           $dbh->STORE(Active => 0);
269 0           1;
270             }
271              
272             sub FETCH ($$) {
273 0     0     my ($dbh, $sAttr) = @_;
274 1     1   18 use experimental qw/smartmatch/;
  1         2  
  1         4  
275 0           given ($sAttr) {
276 0           when ('AutoCommit') { return $dbh->{$sAttr} }
  0            
277 0           when (/^${prefix}_/) { return $dbh->{$sAttr} }
  0            
278 0           default { return $dbh->SUPER::FETCH($sAttr) }
  0            
279             }
280             }
281              
282             sub STORE ($$$) {
283 0     0     my ($dbh, $sAttr, $sValue) = @_;
284 1     1   167 use experimental qw/smartmatch/;
  1         3  
  1         4  
285 0           given ($sAttr) {
286 0           when ('AutoCommit') {
287 0           local $REST::Neo4p::HANDLE = $dbh->{"${prefix}_Handle"};
288 0 0         if (!!$sValue) {
289 0           REST::Neo4p->_set_autocommit;
290 0           $dbh->{$sAttr} = 1;
291             }
292             else {
293 0 0         $dbh->{$sAttr} = 0 if REST::Neo4p->_clear_autocommit;
294             }
295 0           return 1;
296             }
297             # private attributes (neo_)
298 0           when (/^${prefix}_/) {
299 0           $dbh->{$sAttr} = $sValue;
300 0           return 1;
301             }
302 0           default {
303 0           return $dbh->SUPER::STORE($sAttr => $sValue);
304             }
305             }
306             }
307              
308             sub DESTROY($) {
309 0     0     my($dbh) = @_;
310 0           $dbh->disconnect;
311             # deal with the REST::Neo4p object
312             }
313              
314             package #hide from PAUSE
315             DBD::Neo4p::st;
316             $DBD::Neo4p::st::imp_data_size = 0;
317              
318             sub bind_param ($$$;$) {
319 0     0     my($sth, $param, $value, $attribs) = @_;
320 0 0         return $sth->DBI::set_err(2, "Can't bind_param $param, too big")
321             if ($param > $sth->FETCH('NUM_OF_PARAMS'));
322 0           $sth->{"${prefix}_param_values"}->[$param-1] = $value;
323 0           return 1;
324             }
325              
326             sub execute($@) {
327 0     0     my ($sth, @bind_values) = @_;
328              
329 0 0         $sth->finish if $sth->{Active}; # DBI::DBD example, follow up...
330              
331 0 0         my $params = @bind_values ? \@bind_values : $sth->{"${prefix}_param_values"};
332 0 0         unless (@$params == $sth->FETCH('NUM_OF_PARAMS')) {
333 0           return $sth->set_err($DBI::stderr, "Wrong number of parameters");
334             }
335             # Execute
336             # by this time, I know all my parameters
337             # so create the Query obj here
338 0           local $REST::Neo4p::HANDLE = $sth->{Database}->{"${prefix}_Handle"};
339              
340             # per DBI spec, begin work under the hood if AutoCommit is FALSE:
341 0 0         unless ($sth->{Database}->FETCH('AutoCommit')) {
342 0 0         unless (REST::Neo4p->_transaction) {
343 0           REST::Neo4p->begin_work;
344             }
345             }
346              
347 0           my %params;
348 0           @params{@{$sth->{"${prefix}_param_names"}}} = @$params;
  0            
349             my $q = $sth->{"${prefix}_query_obj"} = REST::Neo4p::Query->new(
350 0           $sth->{Statement}, \%params
351             );
352 0           $q->{ResponseAsObjects} = $sth->{Database}->{"${prefix}_ResponseAsObjects"};
353              
354 0           my $numrows = $q->execute;
355 0 0         if ($q->err) {
356 0           return $sth->set_err($DBI::stderr,$q->errstr.' ('.$q->err.')');
357             }
358              
359 0           $sth->{"${prefix}_rows"} = $numrows;
360             # don't know why I have to do the following, when the FETCH
361             # method delegates this to the query object and $sth->{NUM_OF_FIELDS}
362             # thereby returns the correct number, but $sth->_set_bav($row) segfaults
363             # if I don't:
364 0           $sth->STORE(NAME => $q->{NAME});
365 0           $sth->STORE(NUM_OF_FIELDS => 0);
366 0 0         $sth->STORE(NUM_OF_FIELDS => $q ? $q->{NUM_OF_FIELDS} : undef);
367              
368 0           $sth->{Active} = 1;
369 0   0       return $numrows || '0E0';
370             }
371              
372             sub fetch ($) {
373 0     0     my ($sth) = @_;
374 0           my $q =$sth->{"${prefix}_query_obj"};
375 0 0         unless ($q) {
376 0           return $sth->set_err($DBI::stderr, "Query not yet executed");
377             }
378 0           my $row;
379 0           eval {
380 0           $row = $q->fetch;
381             };
382 0 0         if (my $e = Exception::Class->caught) {
383 0           $sth->finish;
384 0 0         return $sth->set_err($DBI::stderr, ref $e ? ref($e)." : ".$e->message : $e);
385             }
386 0 0         if ($q->err) {
387 0           $sth->finish;
388 0           return $sth->set_err($DBI::stderr,$q->errstr.' ('.$q->err.')');
389             }
390            
391 0 0         unless ($row) {
392 0           $sth->STORE(Active => 0);
393 0           return undef;
394             }
395 0           $sth->STORE(NAME => $q->{NAME});
396 0           $sth->STORE(NUM_OF_FIELDS => $q->{NUM_OF_FIELDS});
397 0           $sth->_set_fbav($row);
398             }
399              
400             *fetchrow_arrayref = \&fetch;
401              
402             # override fetchall_hashref - create a sensible hash key from node,
403             # relationship structures
404             sub fetchall_hashref {
405 0     0     my ($sth, $key_field) = @_;
406 0           my @keys;
407 0 0         push @keys, ref $key_field ? @{$key_field} : $key_field;
  0            
408 0           my @names = @{$sth->FETCH($sth->{Database}->{FetchHashKeyName})};
  0            
409 0           for my $key (@keys) {
410 0           my $qkey = quotemeta $key;
411 0 0         unless (grep(/^$qkey$/, @names)) {
412 0           return $sth->set_err($DBI::stderr, "'$key_field' not a column name");
413             }
414              
415             }
416 0           my $rows = $sth->fetchall_arrayref;
417 0           my $ret = {};
418 0 0         return unless $rows;
419 1     1   1106 use experimental qw/smartmatch/;
  1         8  
  1         5  
420 0           for my $row (@$rows) {
421 0           my %data;
422 0           @data{@names} = @$row;
423 0           my $h = $ret;
424 0           for my $k (@keys) {
425 0           my $key_from_data;
426 0           given (ref $data{$k}) {
427 0           when (!$_) {
428 0           $key_from_data = $data{$k};
429             }
430 0           when (/REST::Neo4p/) {
431 0           $key_from_data = ${$data{$k}}; # id
  0            
432             }
433 0           when (/HASH|ARRAY/) {
434 0   0       $key_from_data = $data{$k}{_node} || $data{$k}{_relationship};
435 0 0         $key_from_data = JSON->new->utf8->encode($data{$k}) unless $key_from_data;
436             }
437 0           default {
438 0           die "whaaa? (fetchall_hashref)";
439             }
440             }
441 0           $h->{$key_from_data} = {};
442 0           $h = $h->{$key_from_data};
443             }
444 0           for my $n (@names) {
445 0           my $qn = quotemeta $n;
446 0 0         next if grep /^$qn$/,@keys;
447 0           $h->{$n} = $data{$n};
448             }
449             }
450              
451 0           return $ret;
452             }
453             sub rows ($) {
454 0     0     my($sth) = @_;
455 0           return $sth->{"${prefix}_rows"};
456             }
457              
458             sub finish ($) {
459 0     0     my ($sth) = @_;
460             $sth->{"${prefix}_query_obj"}->finish()
461 0 0         if (defined($sth->{"${prefix}_query_obj"}));
462 0           $sth->{"${prefix}_query_obj"} = undef;
463 0           $sth->STORE(Active => 0);
464 0           $sth->SUPER::finish();
465 0           return 1;
466             }
467              
468             sub FETCH ($$) {
469 0     0     my ($sth, $attrib) = @_;
470 0           my $q =$sth->{"${prefix}_query_obj"};
471 1     1   518 use experimental qw/smartmatch/;
  1         2  
  1         4  
472 0           given ($attrib) {
473 0           when ('TYPE') {
474 0           return;
475             }
476 0           when ('PRECISION') {
477 0           return;
478             }
479 0           when ('SCALE') {
480 0           return;
481             }
482 0           when ('NULLABLE') {
483 0           return;
484             }
485 0           when ('RowInCache') {
486 0           return;
487             }
488 0           when ('CursorName') {
489 0           return;
490             }
491             # Private driver attributes have neo_ prefix
492 0           when (/^${prefix}_/) {
493 0           return $sth->{$attrib}
494             }
495 0           default {
496 0           return $sth->SUPER::FETCH($attrib)
497             }
498             }
499             }
500              
501             sub STORE ($$$) {
502 0     0     my ($sth, $attrib, $value) = @_;
503 1     1   193 use experimental qw/smartmatch/;
  1         3  
  1         10  
504             #1. Private driver attributes have neo_ prefix
505 0           given ($attrib) {
506 0           when (/^${prefix}_|(?:NAME$)/) {
507 0           $sth->{$attrib} = $value;
508 0           return 1;
509             }
510 0           default {
511 0           return $sth->SUPER::STORE($attrib, $value);
512             }
513             }
514             }
515              
516             sub DESTROY {
517 0     0     my ($sth) = @_;
518 0           undef $sth->{"${prefix}_query_obj"};
519             }
520              
521             #>> Just for no warning-----------------------------------------------
522             $DBD::Neo4p::dr::imp_data_size = 0;
523             $DBD::Neo4p::db::imp_data_size = 0;
524             $DBD::Neo4p::st::imp_data_size = 0;
525             *DBD::Neo4p::st::fetchrow_arrayref = \&DBD::Neo4p::st::fetch;
526             #<< Just for no warning------------------------------------------------
527             1;
528             __END__