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   370 use 5.010;
  17         424  
2 17     17   140 use strict;
  17         503  
  17         477  
3 17     17   101 use warnings;
  17         26  
  17         517  
4 17     17   91 use utf8;
  17         31  
  17         107  
5              
6             package Neo4j::Driver::Transaction;
7             # ABSTRACT: Logical container for an atomic unit of work
8             $Neo4j::Driver::Transaction::VERSION = '0.39';
9              
10 17     17   1079 use Carp qw(croak);
  17         31  
  17         1443  
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   483 use Scalar::Util qw(blessed);
  17         29  
  17         857  
18              
19 17     17   115 use Neo4j::Driver::Result;
  17         36  
  17         10732  
20              
21              
22             sub new {
23             # uncoverable pod (private method)
24 257     257 0 599 my ($class, $session, $mode) = @_;
25            
26 257         488 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   71 error_handler => sub { $events->trigger(error => shift) },
36 257         2074 };
37            
38 257         1109 return bless $transaction, $class;
39             }
40              
41              
42             sub run {
43 276     276 1 36047 my ($self, $query, @parameters) = @_;
44            
45 276 100       675 croak 'Transaction already closed' unless $self->is_open;
46            
47 272 100       725 warnings::warnif deprecated => __PACKAGE__ . "->{return_graph} is deprecated" if $self->{return_graph};
48            
49 272         1716 my @statements;
50 272 100       1193 if (ref $query eq 'ARRAY') {
    100          
51 3         62 warnings::warnif deprecated => "run() with multiple statements is deprecated";
52 3         2994 foreach my $args (@$query) {
53 7         21 push @statements, $self->_prepare(@$args);
54             }
55             }
56             elsif ($query) {
57 222         614 @statements = ( $self->_prepare($query, @parameters) );
58             }
59             else {
60 47         96 @statements = ();
61             }
62            
63 267         961 my @results = $self->{net}->_run($self, @statements);
64            
65 235 100       636 if (scalar @statements <= 1) {
66 233   66     631 my $result = $results[0] // Neo4j::Driver::Result->new;
67 233 100       519 warnings::warnif deprecated => "run() in list context is deprecated" if wantarray;
68 233 100       5306 return wantarray ? $result->list : $result;
69             }
70 2 100       13 return wantarray ? @results : \@results;
71             }
72              
73              
74             sub _prepare {
75 236     236   533 my ($self, $query, @parameters) = @_;
76            
77 236 100       514 if (ref $query) {
78 1 50       15 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         306 my $params;
84 235 100       688 if (ref $parameters[0] eq 'HASH') {
    100          
85 3         10 $params = $parameters[0];
86             }
87             elsif (@parameters) {
88 30 100       113 croak 'Query parameters must be given as hash or hashref' if ref $parameters[0];
89 27 100       153 croak 'Odd number of elements in query parameter hash' if scalar @parameters % 2 != 0;
90 26         86 $params = {@parameters};
91             }
92            
93 231 100 100     867 if ($self->{cypher_params_v2} && defined $params) {
94 28         112 my @params_quoted = map {quotemeta} keys %$params;
  44         137  
95 28         76 my $params_re = join '|', @params_quoted, map {"`$_`"} @params_quoted;
  44         155  
96 28         981 $query =~ s/\{($params_re)}/\$$1/g;
97             }
98            
99 231   100     1172 my $statement = [$query, $params // {}];
100 231         502 return $statement;
101             }
102              
103              
104              
105              
106             package # private
107             Neo4j::Driver::Transaction::Bolt;
108 17     17   141 use parent -norequire => 'Neo4j::Driver::Transaction';
  17         50  
  17         117  
109              
110 17     17   1112 use Carp qw(croak);
  17         36  
  17         876  
111 17     17   103 use Try::Tiny;
  17         33  
  17         11662  
112              
113              
114             sub _begin {
115 16     16   30 my ($self) = @_;
116            
117 16 100       128 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   883 $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         124 };
125 11         194 $self->{net}->{active_tx} = 1;
126 11 100       33 $self->run('BEGIN') unless $self->{bolt_txn};
127 10         48 return $self;
128             }
129              
130              
131             sub _run_autocommit {
132 11     11   20 my ($self, $query, @parameters) = @_;
133            
134 11 100       62 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         9 my $results;
138             try {
139 8     8   710 $results = $self->run($query, @parameters);
140             }
141             catch {
142 2     2   1771 $self->{net}->{active_tx} = 0;
143 2         16 croak $_;
144 8         42 };
145 6         103 $self->{net}->{active_tx} = 0;
146            
147 6 100       59 return $results unless wantarray;
148 1         41 warnings::warnif deprecated => "run() in list context is deprecated";
149 1 50       991 return $results->list if ref $query ne 'ARRAY';
150 0         0 return @$results;
151             }
152              
153              
154             sub commit {
155 7     7   3096 my ($self) = @_;
156            
157 7 100       16 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       12 if ($self->{bolt_txn}) {
161 5         84 $self->{bolt_txn}->commit;
162             }
163             else {
164 0         0 $self->run('COMMIT');
165             }
166 5         55 $self->{closed} = 1;
167 5         19 $self->{net}->{active_tx} = 0;
168             }
169              
170              
171             sub rollback {
172 10     10   1057 my ($self) = @_;
173            
174 10 100       26 croak 'Transaction already closed' unless $self->is_open;
175 9 100       28 croak 'Explicit rollback of a managed transaction' if $self->{managed};
176            
177 8 100       19 if ($self->{bolt_txn}) {
178 5         36 $self->{bolt_txn}->rollback;
179             }
180             else {
181 3         11 $self->run('ROLLBACK');
182             }
183 3         9 $self->{closed} = 1;
184 3         6 $self->{net}->{active_tx} = 0;
185             }
186              
187              
188             sub is_open {
189 32     32   815 my ($self) = @_;
190            
191 32 100       110 return 0 if $self->{closed}; # what is closed stays closed
192 28         70 return $self->{net}->{active_tx};
193             }
194              
195              
196              
197              
198             package # private
199             Neo4j::Driver::Transaction::HTTP;
200 17     17   177 use parent -norequire => 'Neo4j::Driver::Transaction';
  17         63  
  17         118  
201              
202 17     17   1077 use Carp qw(croak);
  17         45  
  17         10247  
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   5955 my ($self, $query, @parameters) = @_;
224            
225 224         563 my $statement = $self->SUPER::_prepare($query, @parameters);
226 219         498 my ($cypher, $parameters) = @$statement;
227            
228 219         733 my $json = { statement => '' . $cypher };
229 219         436 $json->{resultDataContents} = $RESULT_DATA_CONTENTS;
230 219 100       508 $json->{resultDataContents} = $RESULT_DATA_CONTENTS_GRAPH if $self->{return_graph};
231 219 100       551 $json->{includeStats} = \1 if $self->{return_stats};
232 219 100       498 $json->{parameters} = $parameters if %$parameters;
233            
234 219         727 return $json;
235             }
236              
237              
238             sub _begin {
239 55     55   125 my ($self) = @_;
240            
241             # no-op for HTTP
242 55         194 return $self;
243             }
244              
245              
246             sub _run_autocommit {
247 195     195   407 my ($self, $query, @parameters) = @_;
248            
249 195         441 $self->{transaction_endpoint} = $self->{commit_endpoint};
250 195   66     1135 $self->{transaction_endpoint} //= URI->new( $self->{net}->{endpoints}->{new_commit} )->path;
251            
252 195         16019 return $self->run($query, @parameters);
253             }
254              
255              
256             sub commit {
257 25     25   8709 my ($self) = @_;
258            
259 25 100       81 croak 'Use `return` to commit a managed transaction' if $self->{managed};
260            
261 24         85 $self->_run_autocommit;
262             }
263              
264              
265             sub rollback {
266 29     29   13027 my ($self) = @_;
267            
268 29 100       88 croak 'Transaction already closed' unless $self->is_open;
269 16 100       76 croak 'Explicit rollback of a managed transaction' if $self->{managed};
270            
271 15 100       83 $self->{net}->_request($self, 'DELETE') if $self->{transaction_endpoint};
272 15         117 $self->{closed} = 1;
273             }
274              
275              
276             sub is_open {
277 298     298   2781 my ($self) = @_;
278            
279 298 100       1030 return 0 if $self->{closed};
280 277 100       900 return 1 if $self->{unused};
281 47         185 return $self->{net}->_is_active_tx($self);
282             }
283              
284              
285             1;
286              
287             __END__