File Coverage

lib/Neo4j/Driver/Result/Bolt.pm
Criterion Covered Total %
statement 98 99 98.9
branch 36 46 78.2
condition 18 36 50.0
subroutine 12 12 100.0
pod 0 1 100.0
total 164 194 85.0


line stmt bran cond sub pod time code
1 17     17   274 use 5.010;
  17         51  
2 17     17   80 use strict;
  17         33  
  17         360  
3 17     17   74 use warnings;
  17         45  
  17         444  
4 17     17   78 use utf8;
  17         42  
  17         81  
5              
6             package Neo4j::Driver::Result::Bolt;
7             # ABSTRACT: Bolt result handler
8             $Neo4j::Driver::Result::Bolt::VERSION = '0.39';
9              
10             # This package is not part of the public Neo4j::Driver API.
11              
12              
13 17     17   4961 use parent 'Neo4j::Driver::Result';
  17         2849  
  17         94  
14              
15 17     17   860 use Carp qw(croak);
  17         30  
  17         1132  
16             our @CARP_NOT = qw(Neo4j::Driver::Net::Bolt Neo4j::Driver::Result);
17              
18 17     17   106 use Neo4j::Driver::Net::Bolt;
  17         36  
  17         20114  
19              
20              
21             our $gather_results = 0; # 1: detach from the stream immediately (yields JSON-style result; used for testing)
22              
23              
24             sub new {
25             # uncoverable pod (private method)
26 5     5 0 11 my ($class, $params) = @_;
27            
28             # Holding a reference to the Bolt connection is important, because
29             # Neo4j::Bolt automatically closes the session upon object destruction.
30             # Perl uses reference counting to control its garbage collector, so we
31             # need to hold that reference {cxn} until we detach from the stream,
32             # even though we never use the connection object directly.
33            
34             my $self = {
35             attached => 1, # 1: unbuffered records may exist on the stream
36             exhausted => 0, # 1: all records read by the client; fetch() will fail
37             buffer => [],
38             columns => undef,
39             summary => undef,
40             cypher_types => $params->{cypher_types},
41             statement => $params->{statement},
42             cxn => $params->{bolt_connection}, # important to avoid dereferencing the connection
43             stream => $params->{bolt_stream},
44             server_info => $params->{server_info},
45             error_handler => $params->{error_handler},
46 5         35 };
47 5         10 bless $self, $class;
48            
49 5 100       15 return $self->_gather_results if $gather_results;
50            
51 3         40 my @names = $params->{bolt_stream}->field_names;
52 3         30 $self->{result} = { columns => \@names };
53            
54 3         8 return $self;
55             }
56              
57              
58             sub _gather_results {
59 2     2   4 my ($self) = @_;
60            
61 2         5 my $stream = $self->{stream};
62 2         6 my @names = $stream->field_names;
63 2         9 my @data = ();
64 2         6 while ( my @row = $stream->fetch_next ) {
65            
66 2 50 33     21 croak 'next true and failure/success mismatch: ' . $stream->failure . '/' . $stream->success unless $stream->failure == -1 || $stream->success == -1 || ($stream->failure xor $stream->success); # assertion
      25        
      33        
67 2 50 33     34 Neo4j::Driver::Net::Bolt->_trigger_bolt_error( $stream, $self->{error_handler}, $self->{cxn} ) if $stream->failure && $stream->failure != -1;
68            
69 2         13 push @data, { row => \@row, meta => [] };
70             }
71            
72 2 50 33     14 croak 'next false and failure/success mismatch: ' . $stream->failure . '/' . $stream->success unless $stream->failure == -1 || $stream->success == -1 || ($stream->failure xor $stream->success); # assertion
      25        
      33        
73 2 50 33     27 Neo4j::Driver::Net::Bolt->_trigger_bolt_error( $stream, $self->{error_handler}, $self->{cxn} ) if $stream->failure && $stream->failure != -1;
74            
75 2         7 $self->{stream} = undef;
76 2         3 $self->{cxn} = undef;
77             $self->{result} = {
78 2         7 columns => \@names,
79             data => \@data,
80             stats => $stream->update_counts(),
81             };
82 2         19 return $self->_as_fully_buffered;
83             }
84              
85              
86             sub _fetch_next {
87 7     7   13 my ($self) = @_;
88            
89 7 100       30 return $self->SUPER::_fetch_next unless $self->{stream};
90            
91 5         7 my (@row, $record);
92 5         20 @row = $self->{stream}->fetch_next;
93 5 100       61 $record = { row => \@row } if @row;
94            
95 5 100       17 unless ($self->{stream}->success) {
96             # success() == -1 is not an error condition; it simply
97             # means that there are no more records on the stream
98 1         8 Neo4j::Driver::Net::Bolt->_trigger_bolt_error( $self->{stream}, $self->{error_handler}, $self->{cxn} );
99             }
100            
101 4         17 return $self->_init_record( $record );
102             }
103              
104              
105             sub _init_record {
106 6     6   11 my ($self, $record, $cypher_types) = @_;
107            
108 6 100       17 return undef unless $record; ##no critic (ProhibitExplicitReturnUndef)
109            
110 4         8 $record->{column_keys} = $self->{columns};
111 4         12 $self->_deep_bless( $record->{row} );
112 4         27 return bless $record, 'Neo4j::Driver::Record';
113             }
114              
115              
116             sub _deep_bless {
117 52     52   69 my ($self, $data) = @_;
118 52         59 my $cypher_types = $self->{cypher_types};
119            
120 52 100       99 if (ref $data eq 'Neo4j::Bolt::Node') { # node
121 8   100     35 my $node = \( $data->{properties} // {} );
122 8         27 bless $node, $cypher_types->{node};
123             $$node->{_meta} = {
124             id => $data->{id},
125             labels => $data->{labels},
126 8         63 };
127 8 50       16 $cypher_types->{init}->($node) if $cypher_types->{init};
128 8         17 return $node;
129             }
130 44 100       69 if (ref $data eq 'Neo4j::Bolt::Relationship') { # relationship
131 8   100     22 my $rel = \( $data->{properties} // {} );
132 8         20 bless $rel, $cypher_types->{relationship};
133             $$rel->{_meta} = {
134             id => $data->{id},
135             start => $data->{start},
136             end => $data->{end},
137             type => $data->{type},
138 8         60 };
139 8 50       20 $cypher_types->{init}->($rel) if $cypher_types->{init};
140 8         15 return $rel;
141             }
142            
143             # support for Neo4j::Bolt 0.01 data structures (to be phased out)
144 36 100 100     86 if (ref $data eq 'HASH' && defined $data->{_node}) { # node
145 4         12 my $node = bless \$data, $cypher_types->{node};
146             $data->{_meta} = {
147             id => $data->{_node},
148             labels => $data->{_labels},
149 4         13 };
150 4 50       10 $cypher_types->{init}->($node) if $cypher_types->{init};
151 4         9 return $node;
152             }
153 32 100 100     68 if (ref $data eq 'HASH' && defined $data->{_relationship}) { # relationship
154 4         10 my $rel = bless \$data, $cypher_types->{relationship};
155             $data->{_meta} = {
156             id => $data->{_relationship},
157             start => $data->{_start},
158             end => $data->{_end},
159             type => $data->{_type},
160 4         14 };
161 4 50       9 $cypher_types->{init}->($rel) if $cypher_types->{init};
162 4         6 return $rel;
163             }
164            
165 28 100       64 if (ref $data eq 'Neo4j::Bolt::Path') { # path
166 4         28 my $path = bless { path => $data }, $cypher_types->{path};
167 4         7 foreach my $i ( 0 .. $#{$data} ) {
  4         10  
168 4         12 $data->[$i] = $self->_deep_bless($data->[$i]);
169             }
170 4 50       8 $cypher_types->{init}->($path) if $cypher_types->{init};
171 4         8 return $path;
172             }
173            
174 24 100       42 if (ref $data eq 'ARRAY') { # array
175 8         10 foreach my $i ( 0 .. $#{$data} ) {
  8         19  
176 40         69 $data->[$i] = $self->_deep_bless($data->[$i]);
177             }
178 8         17 return $data;
179             }
180 16 100       28 if (ref $data eq 'HASH') { # and neither node nor relationship ==> map
181 4         12 foreach my $key ( keys %$data ) {
182 4         31 $data->{$key} = $self->_deep_bless($data->{$key});
183             }
184 4         9 return $data;
185             }
186            
187 12 100       23 if (ref $data eq '') { # scalar
188 8         16 return $data;
189             }
190 4 50       8 if (ref $data eq 'JSON::PP::Boolean') { # boolean
191 4         10 return $data;
192             }
193            
194 0           die "Assertion failed: unexpected type: " . ref $data;
195             }
196              
197              
198             1;