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   305 use 5.010;
  17         438  
2 17     17   102 use strict;
  17         597  
  17         523  
3 17     17   112 use warnings;
  17         36  
  17         494  
4 17     17   134 use utf8;
  17         43  
  17         94  
5              
6             package Neo4j::Driver::Transaction;
7             # ABSTRACT: Logical container for an atomic unit of work
8             $Neo4j::Driver::Transaction::VERSION = '0.38';
9              
10 17     17   1144 use Carp qw(croak);
  17         51  
  17         1369  
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   135 use Scalar::Util qw(blessed);
  17         40  
  17         873  
18              
19 17     17   115 use Neo4j::Driver::Result;
  17         33  
  17         10909  
20              
21              
22             sub new {
23             # uncoverable pod (private method)
24 256     256 0 605 my ($class, $session, $mode) = @_;
25            
26 256         514 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 16     16   75 error_handler => sub { $events->trigger(error => shift) },
36 256         2021 };
37            
38 256         1058 return bless $transaction, $class;
39             }
40              
41              
42             sub run {
43 274     274 1 38926 my ($self, $query, @parameters) = @_;
44            
45 274 100       628 croak 'Transaction already closed' unless $self->is_open;
46            
47 270 100       654 warnings::warnif deprecated => __PACKAGE__ . "->{return_graph} is deprecated" if $self->{return_graph};
48            
49 270         1671 my @statements;
50 270 100       797 if (ref $query eq 'ARRAY') {
    100          
51 3         70 warnings::warnif deprecated => "run() with multiple statements is deprecated";
52 3         3146 foreach my $args (@$query) {
53 7         24 push @statements, $self->_prepare(@$args);
54             }
55             }
56             elsif ($query) {
57 220         562 @statements = ( $self->_prepare($query, @parameters) );
58             }
59             else {
60 47         78 @statements = ();
61             }
62            
63 265         990 my @results = $self->{net}->_run($self, @statements);
64            
65 235 100       595 if (scalar @statements <= 1) {
66 233   66     594 my $result = $results[0] // Neo4j::Driver::Result->new;
67 233 100       444 warnings::warnif deprecated => "run() in list context is deprecated" if wantarray;
68 233 100       2969 return wantarray ? $result->list : $result;
69             }
70 2 100       16 return wantarray ? @results : \@results;
71             }
72              
73              
74             sub _prepare {
75 234     234   415 my ($self, $query, @parameters) = @_;
76            
77 234 100       472 if (ref $query) {
78 1 50       13 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 233         308 my $params;
84 233 100       710 if (ref $parameters[0] eq 'HASH') {
    100          
85 3         8 $params = $parameters[0];
86             }
87             elsif (@parameters) {
88 30 100       100 croak 'Query parameters must be given as hash or hashref' if ref $parameters[0];
89 27 100       101 croak 'Odd number of elements in query parameter hash' if scalar @parameters % 2 != 0;
90 26         80 $params = {@parameters};
91             }
92            
93 229 100 100     846 if ($self->{cypher_params_v2} && defined $params) {
94 28         122 my @params_quoted = map {quotemeta} keys %$params;
  44         128  
95 28         183 my $params_re = join '|', @params_quoted, map {"`$_`"} @params_quoted;
  44         141  
96 28         986 $query =~ s/\{($params_re)}/\$$1/g;
97             }
98            
99 229   100     972 my $statement = [$query, $params // {}];
100 229         481 return $statement;
101             }
102              
103              
104              
105              
106             package # private
107             Neo4j::Driver::Transaction::Bolt;
108 17     17   131 use parent -norequire => 'Neo4j::Driver::Transaction';
  17         39  
  17         127  
109              
110 17     17   1103 use Carp qw(croak);
  17         33  
  17         838  
111 17     17   103 use Try::Tiny;
  17         60  
  17         11394  
112              
113              
114             sub _begin {
115 16     16   30 my ($self) = @_;
116            
117 16 100       84 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   911 $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         57 };
125 11         199 $self->{net}->{active_tx} = 1;
126 11 100       38 $self->run('BEGIN') unless $self->{bolt_txn};
127 10         49 return $self;
128             }
129              
130              
131             sub _run_autocommit {
132 11     11   24 my ($self, $query, @parameters) = @_;
133            
134 11 100       59 croak "Concurrent transactions are unsupported in Bolt (there is already an open transaction in this session)" if $self->{net}->{active_tx};
135            
136 8         14 $self->{net}->{active_tx} = 1; # run() requires an active tx
137 8         11 my $results;
138             try {
139 8     8   710 $results = $self->run($query, @parameters);
140             }
141             catch {
142 2     2   1841 $self->{net}->{active_tx} = 0;
143 2         16 croak $_;
144 8         43 };
145 6         98 $self->{net}->{active_tx} = 0;
146            
147 6 100       60 return $results unless wantarray;
148 1         47 warnings::warnif deprecated => "run() in list context is deprecated";
149 1 50       998 return $results->list if ref $query ne 'ARRAY';
150 0         0 return @$results;
151             }
152              
153              
154             sub commit {
155 7     7   3432 my ($self) = @_;
156            
157 7 100       17 croak 'Transaction already closed' unless $self->is_open;
158 6 100       25 croak 'Use `return` to commit a managed transaction' if $self->{managed};
159            
160 5 50       16 if ($self->{bolt_txn}) {
161 5         18 $self->{bolt_txn}->commit;
162             }
163             else {
164 0         0 $self->run('COMMIT');
165             }
166 5         13 $self->{closed} = 1;
167 5         13 $self->{net}->{active_tx} = 0;
168             }
169              
170              
171             sub rollback {
172 10     10   1038 my ($self) = @_;
173            
174 10 100       23 croak 'Transaction already closed' unless $self->is_open;
175 9 100       32 croak 'Explicit rollback of a managed transaction' if $self->{managed};
176            
177 8 100       18 if ($self->{bolt_txn}) {
178 5         45 $self->{bolt_txn}->rollback;
179             }
180             else {
181 3         10 $self->run('ROLLBACK');
182             }
183 3         8 $self->{closed} = 1;
184 3         9 $self->{net}->{active_tx} = 0;
185             }
186              
187              
188             sub is_open {
189 32     32   990 my ($self) = @_;
190            
191 32 100       213 return 0 if $self->{closed}; # what is closed stays closed
192 28         68 return $self->{net}->{active_tx};
193             }
194              
195              
196              
197              
198             package # private
199             Neo4j::Driver::Transaction::HTTP;
200 17     17   168 use parent -norequire => 'Neo4j::Driver::Transaction';
  17         61  
  17         83  
201              
202 17     17   1096 use Carp qw(croak);
  17         42  
  17         12841  
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 222     222   5963 my ($self, $query, @parameters) = @_;
224            
225 222         555 my $statement = $self->SUPER::_prepare($query, @parameters);
226 217         476 my ($cypher, $parameters) = @$statement;
227            
228 217         683 my $json = { statement => '' . $cypher };
229 217         425 $json->{resultDataContents} = $RESULT_DATA_CONTENTS;
230 217 100       469 $json->{resultDataContents} = $RESULT_DATA_CONTENTS_GRAPH if $self->{return_graph};
231 217 100       512 $json->{includeStats} = \1 if $self->{return_stats};
232 217 100       429 $json->{parameters} = $parameters if %$parameters;
233            
234 217         672 return $json;
235             }
236              
237              
238             sub _begin {
239 54     54   126 my ($self) = @_;
240            
241             # no-op for HTTP
242 54         176 return $self;
243             }
244              
245              
246             sub _run_autocommit {
247 195     195   418 my ($self, $query, @parameters) = @_;
248            
249 195         455 $self->{transaction_endpoint} = $self->{commit_endpoint};
250 195   66     1130 $self->{transaction_endpoint} //= URI->new( $self->{net}->{endpoints}->{new_commit} )->path;
251            
252 195         15239 return $self->run($query, @parameters);
253             }
254              
255              
256             sub commit {
257 25     25   9776 my ($self) = @_;
258            
259 25 100       80 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   13541 my ($self) = @_;
267            
268 29 100       80 croak 'Transaction already closed' unless $self->is_open;
269 16 100       81 croak 'Explicit rollback of a managed transaction' if $self->{managed};
270            
271 15 100       84 $self->{net}->_request($self, 'DELETE') if $self->{transaction_endpoint};
272 15         123 $self->{closed} = 1;
273             }
274              
275              
276             sub is_open {
277 294     294   1267 my ($self) = @_;
278            
279 294 100       927 return 0 if $self->{closed};
280 274 100       862 return 1 if $self->{unused};
281 47         186 return $self->{net}->_is_active_tx($self);
282             }
283              
284              
285             1;
286              
287             __END__