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   278 use 5.010;
  17         59  
2 17     17   89 use strict;
  17         25  
  17         372  
3 17     17   79 use warnings;
  17         39  
  17         442  
4 17     17   89 use utf8;
  17         41  
  17         79  
5              
6             package Neo4j::Driver::Net::Bolt;
7             # ABSTRACT: Network controller for Neo4j Bolt
8             $Neo4j::Driver::Net::Bolt::VERSION = '0.39';
9              
10             # This package is not part of the public Neo4j::Driver API.
11              
12              
13 17     17   1090 use Carp qw(croak);
  17         37  
  17         1231  
14             our @CARP_NOT = qw(Neo4j::Driver::Transaction Neo4j::Driver::Transaction::Bolt);
15              
16 17     17   115 use Try::Tiny;
  17         49  
  17         994  
17 17     17   110 use URI 1.25;
  17         296  
  17         459  
18              
19 17     17   6691 use Neo4j::Driver::Result::Bolt;
  17         41  
  17         621  
20 17     17   6210 use Neo4j::Driver::ServerInfo;
  17         37  
  17         527  
21 17     17   8460 use Neo4j::Error;
  17         45292  
  17         23770  
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 28 my ($class, $driver) = @_;
40            
41 13 100       26 croak "Concurrent transactions are unsupported in Bolt; use multiple sessions" if $driver->config('concurrent_tx');
42            
43 12         31 my $uri = $driver->config('uri');
44 12 100       86 if (my $auth = $driver->config('auth')) {
45 6 100       34 croak "Only Basic Authentication is supported" if $auth->{scheme} ne 'basic';
46 5         18 $uri = $uri->clone;
47 5         52 $uri->userinfo( $auth->{principal} . ':' . $auth->{credentials} );
48             }
49            
50 11   100     302 my $net_module = $driver->config('net_module') || 'Neo4j::Bolt';
51 11 100       35 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       4 unless eval { require Neo4j::Bolt; 1 };
  1         292  
  0         0  
55             }
56            
57 10         13 my $cxn;
58 10 100       23 if ($driver->config('encrypted')) {
59 1         9 $cxn = $net_module->connect_tls("$uri", {
60             timeout => $driver->config('timeout'),
61             ca_file => $driver->config('trust_ca'),
62             });
63             }
64             else {
65 9         37 $cxn = $net_module->connect( "$uri", $driver->config('timeout') );
66             }
67 10 100       87 $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 9 100       97 cypher_types => $driver->config('cypher_types'),
76             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   13742 my ($self, $ref, $error_handler, $connection) = @_;
88 13         22 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   381 raw => scalar try { $ref->get_failure_details }, # Neo4j::Bolt >= 0.41
94 13 100   13   60 }) if try { $ref->server_errcode || $ref->server_errmsg };
  13 100       626  
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   11856 }) 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   3701 }) if try { $ref->errnum || $ref->errmsg };
  13 100       555  
107            
108             try {
109 13   66 13   573 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     24 }) if try { $cxn->errnum || $cxn->errmsg } && $cxn != $ref;
  6 100 100     244  
115 6         750 $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     41 }) if try { $cxn->errnum || $cxn->errmsg };
  6 100       261  
121 13         6614 };
122            
123 13 100       5024 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   2867 my (undef, $ref) = @_;
130            
131 15         20 my ($errnum, $errmsg);
132 15 50       76 ($errnum, $errmsg) = ($ref->errnum, $ref->errmsg) if $ref->can('errnum');
133 15 100       106 ($errnum, $errmsg) = ($ref->client_errnum, $ref->client_errmsg) if $ref->can('client_errnum');
134            
135 15   100     77 $errmsg //= $BOLT_ERROR{$errnum};
136 15 100       69 return "Bolt error $errnum: $errmsg" if $errmsg;
137 8         42 return "Bolt error $errnum";
138             }
139              
140              
141             sub _server {
142 9     9   17 my ($self) = @_;
143            
144 9         18 my $cxn = $self->{connection};
145             return $self->{server_info} = Neo4j::Driver::ServerInfo->new({
146             uri => $self->{uri},
147 9 100       34 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         30 $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   25 my ($self, $tx, @statements) = @_;
164            
165 13 100       42 die "multiple statements not supported for Bolt" if @statements > 1;
166 12         22 my ($statement) = @statements;
167            
168 12         46 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         18 my ($stream, $result);
176 12 100       33 if ($statement->[0]) {
177 10         88 $stream = $query_runner->run_query( @$statement, $self->{database} );
178            
179 7 100 66     82 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         6 $self->{active_tx} = 0;
185 2         7 $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         70 });
196             }
197            
198 7         27 return ($result);
199             }
200              
201              
202             sub _new_tx {
203 11     11   23 my ($self, $driver_tx) = @_;
204            
205 11         17 my $params = {};
206 11 100       46 $params->{mode} = lc substr $driver_tx->{mode}, 0, 1 if $driver_tx->{mode};
207            
208 11         25 my $transaction = "$self->{net_module}::Txn";
209 11 100       76 return unless $transaction->can('new');
210 10         36 return $transaction->new( $self->{connection}, $params, $self->{database} );
211             }
212              
213              
214             1;