File Coverage

lib/Neo4j/Driver/Net/Bolt.pm
Criterion Covered Total %
statement 93 93 100.0
branch 54 56 96.4
condition 14 16 87.5
subroutine 15 15 100.0
pod 0 1 100.0
total 176 181 97.7


line stmt bran cond sub pod time code
1 20     20   285521 use v5.14;
  20         90  
2 20     20   94 use warnings;
  20         52  
  20         1549  
3              
4             package Neo4j::Driver::Net::Bolt 1.02;
5             # ABSTRACT: Network controller for Neo4j Bolt
6              
7              
8             # This package is not part of the public Neo4j::Driver API.
9              
10              
11 20     20   112 use Carp qw(croak);
  20         50  
  20         1699  
12             our @CARP_NOT = qw(Neo4j::Driver::Transaction Neo4j::Driver::Transaction::Bolt);
13 20     20   808 use Feature::Compat::Try;
  20         486  
  20         164  
14              
15 20     20   13028 use Neo4j::Driver::Result::Bolt;
  20         47  
  20         761  
16 20     20   8195 use Neo4j::Driver::ServerInfo;
  20         58  
  20         832  
17 20     20   9791 use Neo4j::Error;
  20         62942  
  20         34542  
18              
19              
20             my $RESULT_MODULE = 'Neo4j::Driver::Result::Bolt';
21              
22             our $verify_version = 1;
23              
24              
25             sub new {
26             # uncoverable pod
27 28     28 0 78 my ($class, $driver) = @_;
28            
29 28 100       83 croak "Concurrent transactions are unsupported in Bolt; use multiple sessions" if $driver->config('concurrent_tx');
30            
31 27         90 my $uri = $driver->config('uri');
32 27 100       84 if (my $auth = $driver->config('auth')) {
33 6 100       50 croak "Only Basic Authentication is supported" if $auth->{scheme} ne 'basic';
34 5         27 $uri = $uri->clone;
35 5         67 $uri->userinfo( $auth->{principal} . ':' . $auth->{credentials} );
36             }
37            
38 26   100     859 my $net_module = $driver->{config}->{net_module} || 'Neo4j::Bolt';
39 26 100 100     142 _verify_version() if $verify_version && $net_module eq 'Neo4j::Bolt';
40            
41 18         64 my $cxn;
42 18 100       65 if ($driver->config('encrypted')) {
43 1         7 $cxn = $net_module->connect_tls("$uri", {
44             timeout => $driver->config('timeout'),
45             ca_file => $driver->config('trust_ca'),
46             });
47             }
48             else {
49 17         88 $cxn = $net_module->connect( "$uri", $driver->config('timeout') );
50             }
51 17 100       208 $class->_trigger_bolt_error( $cxn, $driver->{events} ) unless $cxn->connected;
52            
53             return bless {
54             net_module => $net_module,
55             connection => $cxn,
56             uri => $uri,
57             result_module => $net_module->can('result_handlers') ? ($net_module->result_handlers)[0] : $RESULT_MODULE,
58             server_info => $driver->{server_info},
59 16 100       371 active_tx => 0,
60             }, $class;
61             }
62              
63              
64             # Some Neo4j::Client versions are known to provide broken versions of the lib.
65             # Known-good module version pairs:
66             # +-- Neo4j::Bolt
67             # | +-- Neo4j::Client
68             # | | +-- max recommended Neo4j server
69             # | | |
70             # 0.5000 0.54 5.x
71             # 0.4203 0.46 4.4
72             # 0.20 0.17 3.4
73             # 0.12 - 3.4 (system libneo4j-client)
74             sub _verify_version {
75             # Running this check once (for the first session) is enough.
76 15     15   31 $verify_version = 0;
77            
78 15         31 try {
79 15         456 require Neo4j::Bolt;
80 14         268 my $bolt_version = Neo4j::Bolt->VERSION('0.4201');
81            
82 10 100       83 return if $bolt_version ge '0.5000';
83 8   100     35 my $client_version = eval { Neo4j::Client->VERSION } // '';
  8         72  
84 8 100       59 $client_version =~ m/^0\.5[012]$/ and die
85             sprintf "Installed Neo4j::Client version %s is defective (known-good versions are 0.46 and 0.54 or later; you may also need to reinstall Neo4j::Bolt)\n", $client_version;
86             }
87             catch ($e) {
88 8         478 $e =~ s/\.?\s*$//;
89 8         168 croak sprintf "Protocol scheme 'bolt' is not supported (Neo4j::Bolt not installed).\nNeo4j::Driver will support 'bolt' URLs if the Neo4j::Bolt module is installed.\n%s", $e;
90             }
91             }
92              
93              
94             # Trigger an error using the given event handler.
95             # Meant to only be called after a failure has occurred.
96             # May also be called as class method.
97             # $ref may be a Neo4j::Bolt ResultStream, Cxn, Txn.
98             # $error_handler may be a coderef or the event manager.
99             sub _trigger_bolt_error {
100 12     12   40149 my ($self, $ref, $error_handler, $connection) = @_;
101 12         31 my $error = 'Neo4j::Error';
102            
103             $error = $error->append_new( Server => {
104             code => scalar $ref->server_errcode,
105             message => scalar $ref->server_errmsg,
106             raw => scalar $ref->get_failure_details,
107 12 100       25 }) if eval { $ref->server_errcode || $ref->server_errmsg };
  12 100       94  
108            
109             $error = $error->append_new( Network => {
110             code => scalar $ref->client_errnum,
111             message => scalar $ref->client_errmsg,
112             as_string => $self->_bolt_error($ref),
113 12 100       13399 }) if eval { $ref->client_errnum || $ref->client_errmsg };
  12 100       81  
114            
115             $error = $error->append_new( Network => {
116             code => scalar $ref->errnum,
117             message => scalar $ref->errmsg,
118             as_string => $self->_bolt_error($ref),
119 12 100       5233 }) if eval { $ref->errnum || $ref->errmsg };
  12 100       49  
120            
121 12         7146 try {
122 12   66     143 my $cxn = $connection // $self->{connection};
123             $error = $error->append_new( Network => {
124             code => scalar $cxn->errnum,
125             message => scalar $cxn->errmsg,
126             as_string => $self->_bolt_error($cxn),
127 5 100 100     12 }) if eval { $cxn->errnum || $cxn->errmsg } && $cxn != $ref;
  5 100       14  
128 5         845 $cxn->reset_cxn;
129             $error = $error->append_new( Internal => { # perlbolt#51
130             code => scalar $cxn->errnum,
131             message => scalar $cxn->errmsg,
132             as_string => $self->_bolt_error($cxn),
133 5 100       32 }) if eval { $cxn->errnum || $cxn->errmsg };
  5 100       30  
134             }
135             catch ($e) {}
136            
137 12 100       3668 return $error_handler->($error) if ref $error_handler eq 'CODE';
138 1         9 $error_handler->trigger(error => $error);
139             }
140              
141              
142             sub _bolt_error {
143 13     13   5329 my (undef, $ref) = @_;
144            
145 13         29 my ($errnum, $errmsg);
146 13 50       114 ($errnum, $errmsg) = ($ref->errnum, $ref->errmsg) if $ref->can('errnum');
147 13 100       148 ($errnum, $errmsg) = ($ref->client_errnum, $ref->client_errmsg) if $ref->can('client_errnum');
148            
149 13 100       124 return "Bolt error $errnum: $errmsg" if $errmsg;
150 8         76 return "Bolt error $errnum";
151             }
152              
153              
154             sub _server {
155 9     9   23 my ($self) = @_;
156            
157 9         28 my $cxn = $self->{connection};
158             return $self->{server_info} = Neo4j::Driver::ServerInfo->new({
159             uri => $self->{uri},
160 9         84 version => $cxn->server_id,
161             protocol => $cxn->protocol_version,
162             });
163             }
164              
165              
166             # Update requested database name.
167             sub _set_database {
168 9     9   19 my ($self, $database) = @_;
169            
170 9         26 $self->{database} = $database;
171             }
172              
173              
174             # Send queries to the Neo4j server and return a list of all results.
175             sub _run {
176 11     11   45 my ($self, $tx, @queries) = @_;
177            
178 11 50       37 die "multiple queries not supported for Bolt" if @queries > 1;
179 11         26 my ($query) = @queries;
180            
181 11 100       36 my $query_runner = $tx->{bolt_txn} ? $tx->{bolt_txn} : $self->{connection};
182            
183 11         23 my ($stream, $result);
184 11 100       32 if ($query->[0]) {
185 10         169 $stream = $query_runner->run_query( @$query, $self->{database} );
186            
187 7 100 66     119 if (! $stream || $stream->failure) {
188             # failure() == -1 is an error condition because run_query_()
189             # always calls update_errstate_rs_obj()
190            
191 1         7 $tx->{closed} = 1;
192 1         1 $self->{active_tx} = 0;
193 1         6 $self->_trigger_bolt_error( $stream, $tx->{error_handler} );
194             }
195            
196             $result = $self->{result_module}->new({
197             bolt_stream => $stream,
198             bolt_connection => $self->{connection},
199             query => $query,
200             server_info => $self->{server_info},
201             error_handler => $tx->{error_handler},
202 6         117 });
203             }
204             else {
205 1         10 $result = Neo4j::Driver::Result->new;
206 1         8 $result->{server_info} = $self->{server_info};
207             }
208            
209 7         38 return ($result);
210             }
211              
212              
213             sub _new_tx {
214 11     11   26 my ($self, $driver_tx) = @_;
215            
216 11         24 my $params = {};
217 11 100       58 $params->{mode} = lc substr $driver_tx->{mode}, 0, 1 if $driver_tx->{mode};
218            
219 11         31 my $transaction = "$self->{net_module}::Txn";
220 11         132 return $transaction->new( $self->{connection}, $params, $self->{database} );
221             }
222              
223              
224             1;