File Coverage

lib/Neo4j/Driver/Net/Bolt.pm
Criterion Covered Total %
statement 96 97 98.9
branch 56 58 96.5
condition 20 26 76.9
subroutine 22 22 100.0
pod 0 1 100.0
total 194 204 95.5


line stmt bran cond sub pod time code
1 17     17   302 use 5.010;
  17         56  
2 17     17   104 use strict;
  17         27  
  17         456  
3 17     17   84 use warnings;
  17         35  
  17         468  
4 17     17   80 use utf8;
  17         43  
  17         74  
5              
6             package Neo4j::Driver::Net::Bolt;
7             # ABSTRACT: Network controller for Neo4j Bolt
8             $Neo4j::Driver::Net::Bolt::VERSION = '0.38';
9              
10             # This package is not part of the public Neo4j::Driver API.
11              
12              
13 17     17   1136 use Carp qw(croak);
  17         36  
  17         1414  
14             our @CARP_NOT = qw(Neo4j::Driver::Transaction Neo4j::Driver::Transaction::Bolt);
15              
16 17     17   126 use Try::Tiny;
  17         128  
  17         996  
17 17     17   117 use URI 1.25;
  17         299  
  17         520  
18              
19 17     17   6887 use Neo4j::Driver::Result::Bolt;
  17         42  
  17         603  
20 17     17   6221 use Neo4j::Driver::ServerInfo;
  17         35  
  17         483  
21 17     17   8233 use Neo4j::Error;
  17         47750  
  17         22891  
22              
23              
24             # Neo4j::Bolt < 0.10 didn't report human-readable error messages
25             # (perlbolt#24), so we re-create the most common ones here
26             my %BOLT_ERROR = (
27             61 => "Connection refused",
28             -13 => "Unknown host",
29             -14 => "Could not agree on a protocol version",
30             -15 => "Username or password is invalid",
31             -22 => "Statement evaluation failed",
32             );
33              
34             my $RESULT_MODULE = 'Neo4j::Driver::Result::Bolt';
35              
36              
37             sub new {
38             # uncoverable pod
39 13     13 0 30 my ($class, $driver) = @_;
40            
41 13 100       43 croak "Concurrent transactions are unsupported in Bolt; use multiple sessions" if $driver->{concurrent_tx};
42            
43 12         30 my $uri = $driver->{uri};
44 12 100       29 if ($driver->{auth}) {
45 6 100       32 croak "Only Basic Authentication is supported" if $driver->{auth}->{scheme} ne 'basic';
46 5         19 $uri = $uri->clone;
47 5         50 $uri->userinfo( $driver->{auth}->{principal} . ':' . $driver->{auth}->{credentials} );
48             }
49            
50 11   100     302 my $net_module = $driver->{net_module} || 'Neo4j::Bolt';
51 11 100       27 if ($net_module eq 'Neo4j::Bolt') {
52             croak "Protocol scheme 'bolt' is not supported (Neo4j::Bolt not installed)\n"
53             . "Neo4j::Driver will support 'bolt' URLs if the Neo4j::Bolt module is installed.\n"
54 1 50       3 unless eval { require Neo4j::Bolt; 1 };
  1         344  
  0         0  
55             }
56            
57 10         11 my $cxn;
58 10 100       24 if ($driver->{tls}) {
59             $cxn = $net_module->connect_tls("$uri", {
60             timeout => $driver->{http_timeout},
61             ca_file => $driver->{tls_ca},
62 1         5 });
63             }
64             else {
65 9         29 $cxn = $net_module->connect( "$uri", $driver->{http_timeout} );
66             }
67 10 100       211 $class->_trigger_bolt_error( $cxn, $driver->{plugins} ) unless $cxn->connected;
68            
69             return bless {
70             net_module => $net_module,
71             connection => $cxn,
72             uri => $uri,
73             result_module => $net_module->can('result_handlers') ? ($net_module->result_handlers)[0] : $RESULT_MODULE,
74             server_info => $driver->{server_info},
75             cypher_types => $driver->{cypher_types},
76 9 100       170 active_tx => 0,
77             }, $class;
78             }
79              
80              
81             # Trigger an error using the given event handler.
82             # Meant to only be called after a failure has occurred.
83             # May also be called as class method.
84             # $ref may be a Neo4j::Bolt ResultStream, Cxn, Txn.
85             # $error_handler may be a coderef or the event manager.
86             sub _trigger_bolt_error {
87 13     13   13747 my ($self, $ref, $error_handler, $connection) = @_;
88 13         20 my $error = 'Neo4j::Error';
89            
90             $error = $error->append_new( Server => {
91             code => scalar $ref->server_errcode,
92             message => scalar $ref->server_errmsg,
93 5     5   359 raw => scalar try { $ref->get_failure_details }, # Neo4j::Bolt >= 0.41
94 13 100   13   66 }) if try { $ref->server_errcode || $ref->server_errmsg };
  13 100       636  
95            
96             $error = $error->append_new( Network => {
97             code => scalar $ref->client_errnum,
98             message => scalar $ref->client_errmsg // $BOLT_ERROR{$ref->client_errnum},
99             as_string => $self->_bolt_error($ref),
100 13 100 66 13   11533 }) if try { $ref->client_errnum || $ref->client_errmsg };
  13 100       542  
101            
102             $error = $error->append_new( Network => {
103             code => scalar $ref->errnum,
104             message => scalar $ref->errmsg // $BOLT_ERROR{$ref->errnum},
105             as_string => $self->_bolt_error($ref),
106 13 100 100 13   3727 }) if try { $ref->errnum || $ref->errmsg };
  13 100       502  
107            
108             try {
109 13   66 13   559 my $cxn = $connection // $self->{connection};
110             $error = $error->append_new( Network => {
111             code => scalar $cxn->errnum,
112             message => scalar $cxn->errmsg // $BOLT_ERROR{$cxn->errnum},
113             as_string => $self->_bolt_error($cxn),
114 6 100 33     31 }) if try { $cxn->errnum || $cxn->errmsg } && $cxn != $ref;
  6 100 100     272  
115 6         813 $cxn->reset_cxn;
116             $error = $error->append_new( Internal => { # perlbolt#51
117             code => scalar $cxn->errnum,
118             message => scalar $cxn->errmsg // $BOLT_ERROR{$cxn->errnum},
119             as_string => $self->_bolt_error($cxn),
120 6 100 66     40 }) if try { $cxn->errnum || $cxn->errmsg };
  6 100       246  
121 13         6704 };
122            
123 13 100       5373 return $error_handler->($error) if ref $error_handler eq 'CODE';
124 1         5 $error_handler->trigger(error => $error);
125             }
126              
127              
128             sub _bolt_error {
129 15     15   2910 my (undef, $ref) = @_;
130            
131 15         18 my ($errnum, $errmsg);
132 15 50       76 ($errnum, $errmsg) = ($ref->errnum, $ref->errmsg) if $ref->can('errnum');
133 15 100       103 ($errnum, $errmsg) = ($ref->client_errnum, $ref->client_errmsg) if $ref->can('client_errnum');
134            
135 15   100     83 $errmsg //= $BOLT_ERROR{$errnum};
136 15 100       64 return "Bolt error $errnum: $errmsg" if $errmsg;
137 8         48 return "Bolt error $errnum";
138             }
139              
140              
141             sub _server {
142 9     9   18 my ($self) = @_;
143            
144 9         66 my $cxn = $self->{connection};
145             return $self->{server_info} = Neo4j::Driver::ServerInfo->new({
146             uri => $self->{uri},
147 9 100       39 version => $cxn->server_id,
148             protocol => $cxn->can('protocol_version') ? $cxn->protocol_version : '',
149             });
150             }
151              
152              
153             # Update requested database name.
154             sub _set_database {
155 9     9   21 my ($self, $database) = @_;
156            
157 9         29 $self->{database} = $database;
158             }
159              
160              
161             # Send statements to the Neo4j server and return a list of all results.
162             sub _run {
163 13     13   72 my ($self, $tx, @statements) = @_;
164            
165 13 100       37 die "multiple statements not supported for Bolt" if @statements > 1;
166 12         29 my ($statement) = @statements;
167            
168 12         35 my $statement_json = {
169             statement => $statement->[0],
170             parameters => $statement->[1],
171             };
172            
173 12 100       38 my $query_runner = $tx->{bolt_txn} ? $tx->{bolt_txn} : $self->{connection};
174            
175 12         20 my ($stream, $result);
176 12 100       27 if ($statement->[0]) {
177 10         77 $stream = $query_runner->run_query( @$statement, $self->{database} );
178            
179 7 100 66     86 if (! $stream || $stream->failure) {
180             # failure() == -1 is an error condition because run_query_()
181             # always calls update_errstate_rs_obj()
182            
183 2         10 $tx->{closed} = 1;
184 2         4 $self->{active_tx} = 0;
185 2         8 $self->_trigger_bolt_error( $stream, $tx->{error_handler} );
186             }
187            
188             $result = $self->{result_module}->new({
189             bolt_stream => $stream,
190             bolt_connection => $self->{connection},
191             statement => $statement_json,
192             cypher_types => $self->{cypher_types},
193             server_info => $self->{server_info},
194             error_handler => $tx->{error_handler},
195 5         66 });
196             }
197            
198 7         25 return ($result);
199             }
200              
201              
202             sub _new_tx {
203 11     11   19 my ($self, $driver_tx) = @_;
204            
205 11         20 my $params = {};
206 11 100       44 $params->{mode} = lc substr $driver_tx->{mode}, 0, 1 if $driver_tx->{mode};
207            
208 11         23 my $transaction = "$self->{net_module}::Txn";
209 11 100       165 return unless $transaction->can('new');
210 10         38 return $transaction->new( $self->{connection}, $params, $self->{database} );
211             }
212              
213              
214             1;