File Coverage

lib/Neo4j/Driver/Transaction.pm
Criterion Covered Total %
statement 137 147 93.2
branch 69 82 84.1
condition 9 11 81.8
subroutine 30 32 93.7
pod 1 2 100.0
total 246 274 90.1


line stmt bran cond sub pod time code
1 17     17   320 use 5.010;
  17         410  
2 17     17   124 use strict;
  17         497  
  17         452  
3 17     17   95 use warnings;
  17         31  
  17         590  
4 17     17   93 use utf8;
  17         28  
  17         117  
5              
6             package Neo4j::Driver::Transaction;
7             # ABSTRACT: Logical container for an atomic unit of work
8             $Neo4j::Driver::Transaction::VERSION = '0.40';
9              
10 17     17   1037 use Carp qw(croak);
  17         31  
  17         1428  
11             our @CARP_NOT = qw(
12             Neo4j::Driver::Session
13             Neo4j::Driver::Session::Bolt
14             Neo4j::Driver::Session::HTTP
15             Try::Tiny
16             );
17 17     17   124 use Scalar::Util qw(blessed);
  17         36  
  17         990  
18              
19 17     17   107 use Neo4j::Driver::Result;
  17         24  
  17         11072  
20              
21              
22             sub new {
23             # uncoverable pod (private method)
24 257     257 0 625 my ($class, $session, $mode) = @_;
25            
26 257         461 my $events = $session->{driver}->{plugins};
27             my $transaction = {
28             cypher_params_v2 => $session->{cypher_params_v2},
29             net => $session->{net},
30             mode => $mode,
31             unused => 1, # for HTTP only
32             closed => 0,
33             return_graph => 0,
34             return_stats => 1,
35 18     18   78 error_handler => sub { $events->trigger(error => shift) },
36 257         1967 };
37            
38 257         1043 return bless $transaction, $class;
39             }
40              
41              
42             sub run {
43 276     276 1 37314 my ($self, $query, @parameters) = @_;
44            
45 276 100       652 croak 'Transaction already closed' unless $self->is_open;
46            
47 272 100       660 warnings::warnif deprecated => __PACKAGE__ . "->{return_graph} is deprecated" if $self->{return_graph};
48            
49 272         1675 my @statements;
50 272 100       752 if (ref $query eq 'ARRAY') {
    100          
51 3         66 warnings::warnif deprecated => "run() with multiple statements is deprecated";
52 3         3020 foreach my $args (@$query) {
53 7         19 push @statements, $self->_prepare(@$args);
54             }
55             }
56             elsif ($query) {
57 222         566 @statements = ( $self->_prepare($query, @parameters) );
58             }
59             else {
60 47         90 @statements = ();
61             }
62            
63 267         966 my @results = $self->{net}->_run($self, @statements);
64            
65 235 100       591 if (scalar @statements <= 1) {
66 233   66     553 my $result = $results[0] // Neo4j::Driver::Result->new;
67 233 100       505 warnings::warnif deprecated => "run() in list context is deprecated" if wantarray;
68 233 100       5171 return wantarray ? $result->list : $result;
69             }
70 2 100       30 return wantarray ? @results : \@results;
71             }
72              
73              
74             sub _prepare {
75 236     236   409 my ($self, $query, @parameters) = @_;
76            
77 236 100       441 if (ref $query) {
78 1 50       14 croak 'Query cannot be unblessed reference' unless blessed $query;
79             # REST::Neo4p::Query->query is not part of the documented API
80 0 0       0 $query = '' . $query->query if $query->isa('REST::Neo4p::Query');
81             }
82            
83 235         309 my $params;
84 235 100       701 if (ref $parameters[0] eq 'HASH') {
    100          
85 3         11 $params = $parameters[0];
86             }
87             elsif (@parameters) {
88 30 100       115 croak 'Query parameters must be given as hash or hashref' if ref $parameters[0];
89 27 100       146 croak 'Odd number of elements in query parameter hash' if scalar @parameters % 2 != 0;
90 26         87 $params = {@parameters};
91             }
92            
93 231 100 100     775 if ($self->{cypher_params_v2} && defined $params) {
94 28         115 my @params_quoted = map {quotemeta} keys %$params;
  44         123  
95 28         82 my $params_re = join '|', @params_quoted, map {"`$_`"} @params_quoted;
  44         167  
96 28         998 $query =~ s/\{($params_re)}/\$$1/g;
97             }
98            
99 231   100     1072 my $statement = [$query, $params // {}];
100 231         484 return $statement;
101             }
102              
103              
104              
105              
106             package # private
107             Neo4j::Driver::Transaction::Bolt;
108 17     17   193 use parent -norequire => 'Neo4j::Driver::Transaction';
  17         53  
  17         124  
109              
110 17     17   1160 use Carp qw(croak);
  17         36  
  17         934  
111 17     17   104 use Try::Tiny;
  17         28  
  17         11642  
112              
113              
114             sub _begin {
115 16     16   35 my ($self) = @_;
116            
117 16 100       92 croak "Concurrent transactions are unsupported in Bolt (there is already an open transaction in this session)" if $self->{net}->{active_tx};
118            
119             try {
120 11     11   928 $self->{bolt_txn} = $self->{net}->_new_tx($self);
121             }
122             catch {
123 0 0   0   0 die $_ if $_ !~ m/\bprotocol version\b/i; # Bolt v1/v2
124 11         144 };
125 11         201 $self->{net}->{active_tx} = 1;
126 11 100       32 $self->run('BEGIN') unless $self->{bolt_txn};
127 10         56 return $self;
128             }
129              
130              
131             sub _run_autocommit {
132 11     11   23 my ($self, $query, @parameters) = @_;
133            
134 11 100       64 croak "Concurrent transactions are unsupported in Bolt (there is already an open transaction in this session)" if $self->{net}->{active_tx};
135            
136 8         11 $self->{net}->{active_tx} = 1; # run() requires an active tx
137 8         12 my $results;
138             try {
139 8     8   717 $results = $self->run($query, @parameters);
140             }
141             catch {
142 2     2   1846 $self->{net}->{active_tx} = 0;
143 2         19 croak $_;
144 8         40 };
145 6         105 $self->{net}->{active_tx} = 0;
146            
147 6 100       78 return $results unless wantarray;
148 1         40 warnings::warnif deprecated => "run() in list context is deprecated";
149 1 50       990 return $results->list if ref $query ne 'ARRAY';
150 0         0 return @$results;
151             }
152              
153              
154             sub commit {
155 7     7   3174 my ($self) = @_;
156            
157 7 100       18 croak 'Transaction already closed' unless $self->is_open;
158 6 100       27 croak 'Use `return` to commit a managed transaction' if $self->{managed};
159            
160 5 50       13 if ($self->{bolt_txn}) {
161 5         89 $self->{bolt_txn}->commit;
162             }
163             else {
164 0         0 $self->run('COMMIT');
165             }
166 5         20 $self->{closed} = 1;
167 5         15 $self->{net}->{active_tx} = 0;
168             }
169              
170              
171             sub rollback {
172 10     10   1074 my ($self) = @_;
173            
174 10 100       23 croak 'Transaction already closed' unless $self->is_open;
175 9 100       41 croak 'Explicit rollback of a managed transaction' if $self->{managed};
176            
177 8 100       19 if ($self->{bolt_txn}) {
178 5         33 $self->{bolt_txn}->rollback;
179             }
180             else {
181 3         11 $self->run('ROLLBACK');
182             }
183 3         7 $self->{closed} = 1;
184 3         8 $self->{net}->{active_tx} = 0;
185             }
186              
187              
188             sub is_open {
189 32     32   795 my ($self) = @_;
190            
191 32 100       113 return 0 if $self->{closed}; # what is closed stays closed
192 28         72 return $self->{net}->{active_tx};
193             }
194              
195              
196              
197              
198             package # private
199             Neo4j::Driver::Transaction::HTTP;
200 17     17   165 use parent -norequire => 'Neo4j::Driver::Transaction';
  17         63  
  17         157  
201              
202 17     17   1089 use Carp qw(croak);
  17         73  
  17         10472  
203              
204             # use 'rest' in place of broken 'meta', see neo4j #12306
205             my $RESULT_DATA_CONTENTS = ['row', 'rest'];
206             my $RESULT_DATA_CONTENTS_GRAPH = ['row', 'rest', 'graph'];
207              
208              
209             sub _run_multiple {
210 0     0   0 my ($self, @statements) = @_;
211            
212 0 0       0 croak 'Transaction already closed' unless $self->is_open;
213            
214             return $self->{net}->_run( $self, map {
215 0 0       0 croak '_run_multiple() expects a list of array references' unless ref eq 'ARRAY';
  0         0  
216 0 0       0 croak '_run_multiple() with empty statements not allowed' unless $_->[0];
217 0         0 $self->_prepare(@$_);
218             } @statements );
219             }
220              
221              
222             sub _prepare {
223 224     224   5959 my ($self, $query, @parameters) = @_;
224            
225 224         562 my $statement = $self->SUPER::_prepare($query, @parameters);
226 219         446 my ($cypher, $parameters) = @$statement;
227            
228 219         681 my $json = { statement => '' . $cypher };
229 219         405 $json->{resultDataContents} = $RESULT_DATA_CONTENTS;
230 219 100       481 $json->{resultDataContents} = $RESULT_DATA_CONTENTS_GRAPH if $self->{return_graph};
231 219 100       582 $json->{includeStats} = \1 if $self->{return_stats};
232 219 100       466 $json->{parameters} = $parameters if %$parameters;
233            
234 219         721 return $json;
235             }
236              
237              
238             sub _begin {
239 55     55   125 my ($self) = @_;
240            
241             # no-op for HTTP
242 55         178 return $self;
243             }
244              
245              
246             sub _run_autocommit {
247 195     195   397 my ($self, $query, @parameters) = @_;
248            
249 195         423 $self->{transaction_endpoint} = $self->{commit_endpoint};
250 195   66     1074 $self->{transaction_endpoint} //= URI->new( $self->{net}->{endpoints}->{new_commit} )->path;
251            
252 195         15116 return $self->run($query, @parameters);
253             }
254              
255              
256             sub commit {
257 25     25   8736 my ($self) = @_;
258            
259 25 100       86 croak 'Use `return` to commit a managed transaction' if $self->{managed};
260            
261 24         49 $self->_run_autocommit;
262             }
263              
264              
265             sub rollback {
266 29     29   13441 my ($self) = @_;
267            
268 29 100       85 croak 'Transaction already closed' unless $self->is_open;
269 16 100       84 croak 'Explicit rollback of a managed transaction' if $self->{managed};
270            
271 15 100       98 $self->{net}->_request($self, 'DELETE') if $self->{transaction_endpoint};
272 15         107 $self->{closed} = 1;
273             }
274              
275              
276             sub is_open {
277 298     298   2771 my ($self) = @_;
278            
279 298 100       962 return 0 if $self->{closed};
280 277 100       887 return 1 if $self->{unused};
281 47         198 return $self->{net}->_is_active_tx($self);
282             }
283              
284              
285             1;
286              
287             __END__