File Coverage

lib/Neo4j/Driver/Transaction.pm
Criterion Covered Total %
statement 124 132 93.9
branch 56 68 82.3
condition 9 11 81.8
subroutine 25 26 96.1
pod 1 2 100.0
total 215 239 90.3


line stmt bran cond sub pod time code
1 20     20   271 use v5.14;
  20         97  
2 20     20   122 use warnings;
  20         33  
  20         1637  
3              
4             package Neo4j::Driver::Transaction 1.02;
5             # ABSTRACT: Logical container for an atomic unit of work
6              
7              
8 20     20   128 use Carp qw(croak);
  20         52  
  20         2170  
9             our @CARP_NOT = qw(
10             Neo4j::Driver::Session
11             Neo4j::Driver::Session::Bolt
12             Neo4j::Driver::Session::HTTP
13             );
14 20     20   148 use Scalar::Util qw(blessed);
  20         49  
  20         1267  
15              
16 20     20   130 use Neo4j::Driver::Result;
  20         40  
  20         14194  
17              
18              
19             sub new {
20             # uncoverable pod (private method)
21 280     280 0 794 my ($class, $session, $mode) = @_;
22            
23 280         791 my $events = $session->{driver}->{events};
24             my $transaction = {
25             cypher_params_v2 => $session->{cypher_params_v2},
26             net => $session->{net},
27             mode => $mode,
28             unused => 1, # for HTTP only
29             closed => 0,
30             return_stats => 1,
31 15     15   92 error_handler => sub { $events->trigger(error => shift) },
32 280         2835 };
33            
34 280         1476 return bless $transaction, $class;
35             }
36              
37              
38             sub run {
39 291     291 1 50114 my ($self, $query, @parameters) = @_;
40            
41 291 100       888 croak 'Transaction already closed' unless $self->is_open;
42            
43 287 100       858 croak sprintf 'The %s->{return_graph} feature was removed', __PACKAGE__ if $self->{return_graph};
44            
45 286         484 my @statements;
46 286 100       1102 if (ref $query eq 'ARRAY') {
    100          
47 1         14 croak 'Call run() with a single query statement only';
48             # Consider using the private internal method _run_multiple() if you really have to
49             }
50             elsif ($query) {
51 237         815 @statements = ( $self->_prepare($query, @parameters) );
52             }
53             else {
54 48         92 @statements = ();
55             }
56            
57 280         1394 my @results = $self->{net}->_run($self, @statements);
58            
59 247   66     815 my $result = $results[0] // Neo4j::Driver::Result->new;
60 247         11747 return $result;
61             }
62              
63              
64             sub _prepare {
65 241     241   605 my ($self, $query, @parameters) = @_;
66            
67 241 100       680 if (ref $query) {
68 1 50       17 croak 'Query cannot be unblessed reference' unless blessed $query;
69             # REST::Neo4p::Query->query is not part of the documented API
70 0 0       0 $query = '' . $query->query if $query->isa('REST::Neo4p::Query');
71             }
72            
73 240         394 my $params;
74 240 100       909 if (ref $parameters[0] eq 'HASH') {
    100          
75 2         9 $params = $parameters[0];
76             }
77             elsif (@parameters) {
78 26 100       128 croak 'Query parameters must be given as hash or hashref' if ref $parameters[0];
79 23 100       106 croak 'Odd number of elements in query parameter hash' if scalar @parameters % 2 != 0;
80 22         77 $params = {@parameters};
81             }
82            
83 236 100 100     1086 if ($self->{cypher_params_v2} && defined $params) {
84 23         78 my @params_quoted = map {quotemeta} keys %$params;
  37         144  
85 23         57 my $params_re = join '|', @params_quoted, map {"`$_`"} @params_quoted;
  37         115  
86 23         1024 $query =~ s/\{($params_re)}/\$$1/g;
87             }
88            
89 236   100     1448 my $statement = [$query, $params // {}];
90 236         624 return $statement;
91             }
92              
93              
94              
95              
96             package # private
97             Neo4j::Driver::Transaction::Bolt;
98 20     20   164 use parent -norequire => 'Neo4j::Driver::Transaction';
  20         36  
  20         156  
99              
100 20     20   1485 use Carp qw(croak);
  20         41  
  20         1383  
101 20     20   132 use Feature::Compat::Try;
  20         33  
  20         197  
102              
103              
104             sub _begin {
105 16     16   36 my ($self) = @_;
106            
107 16 100       124 croak "Concurrent transactions are unsupported in Bolt (there is already an open transaction in this session)" if $self->{net}->{active_tx};
108            
109 11         27 try {
110 11         60 $self->{bolt_txn} = $self->{net}->_new_tx($self);
111             }
112             catch ($e) {
113 1 50       27 die $e if $e !~ m/\bprotocol version\b/i; # Bolt v1/v2
114             }
115 10         90 $self->{net}->{active_tx} = 1;
116 10 50       36 $self->run('BEGIN') unless $self->{bolt_txn};
117 10         69 return $self;
118             }
119              
120              
121             sub _run_autocommit {
122 11     11   48 my ($self, $query, @parameters) = @_;
123            
124 11 100       113 croak "Concurrent transactions are unsupported in Bolt (there is already an open transaction in this session)" if $self->{net}->{active_tx};
125            
126 8         22 $self->{net}->{active_tx} = 1; # run() requires an active tx
127 8         17 my $results;
128 8         18 try {
129 8         37 $results = $self->run($query, @parameters);
130             }
131             catch ($e) {
132 2         2615 $self->{net}->{active_tx} = 0;
133 2         22 croak $e;
134             }
135 6         17 $self->{net}->{active_tx} = 0;
136            
137 6         72 return $results;
138             }
139              
140              
141             sub commit {
142 7     7   6275 my ($self) = @_;
143            
144 7 100       21 croak 'Transaction already closed' unless $self->is_open;
145 6 100       54 croak 'Use `return` to commit a managed transaction' if $self->{managed};
146            
147 5 50       21 if ($self->{bolt_txn}) {
148 5         25 $self->{bolt_txn}->commit;
149             }
150             else {
151 0         0 $self->run('COMMIT');
152             }
153 5         18 $self->{closed} = 1;
154 5         23 $self->{net}->{active_tx} = 0;
155             }
156              
157              
158             sub rollback {
159 10     10   1803 my ($self) = @_;
160            
161 10 100       422 croak 'Transaction already closed' unless $self->is_open;
162 9 100       45 croak 'Explicit rollback of a managed transaction' if $self->{managed};
163            
164 8 100       46 if ($self->{bolt_txn}) {
165 5         71 $self->{bolt_txn}->rollback;
166             }
167             else {
168 3         14 $self->run('ROLLBACK');
169             }
170 3         11 $self->{closed} = 1;
171 3         12 $self->{net}->{active_tx} = 0;
172             }
173              
174              
175             sub is_open {
176 31     31   1558 my ($self) = @_;
177            
178 31 100       141 return 0 if $self->{closed}; # what is closed stays closed
179 27         96 return $self->{net}->{active_tx};
180             }
181              
182              
183              
184              
185             package # private
186             Neo4j::Driver::Transaction::HTTP;
187 20     20   20660 use parent -norequire => 'Neo4j::Driver::Transaction';
  20         42  
  20         154  
188              
189 20     20   1577 use Carp qw(croak);
  20         40  
  20         15966  
190              
191             # use 'rest' in place of broken 'meta', see neo4j #12306
192             my $RESULT_DATA_CONTENTS = ['row', 'rest'];
193              
194              
195             sub _run_multiple {
196 0     0   0 my ($self, @statements) = @_;
197            
198 0 0       0 croak 'Transaction already closed' unless $self->is_open;
199            
200             return $self->{net}->_run( $self, map {
201 0 0       0 croak '_run_multiple() expects a list of array references' unless ref eq 'ARRAY';
  0         0  
202 0 0       0 croak '_run_multiple() with empty statements not allowed' unless $_->[0];
203 0         0 $self->_prepare(@$_);
204             } @statements );
205             }
206              
207              
208             sub _prepare {
209 231     231   21268 my ($self, $query, @parameters) = @_;
210            
211 231         788 my $statement = $self->SUPER::_prepare($query, @parameters);
212 226         576 my ($cypher, $parameters) = @$statement;
213            
214 226         955 my $json = { statement => '' . $cypher };
215 226         540 $json->{resultDataContents} = $RESULT_DATA_CONTENTS;
216 226 100       820 $json->{includeStats} = \1 if $self->{return_stats};
217 226 100       654 $json->{parameters} = $parameters if %$parameters;
218            
219 226         904 return $json;
220             }
221              
222              
223             sub _begin {
224 51     51   109 my ($self) = @_;
225            
226             # no-op for HTTP
227 51         172 return $self;
228             }
229              
230              
231             sub _run_autocommit {
232 224     224   552 my ($self, $query, @parameters) = @_;
233            
234 224         703 $self->{transaction_endpoint} = $self->{commit_endpoint};
235 224   66     2138 $self->{transaction_endpoint} //= URI->new( $self->{net}->{endpoints}->{new_commit} )->path;
236            
237 224         25928 return $self->run($query, @parameters);
238             }
239              
240              
241             sub commit {
242 26     26   8087 my ($self) = @_;
243            
244 26 100       82 croak 'Use `return` to commit a managed transaction' if $self->{managed};
245            
246 25         59 $self->_run_autocommit;
247             }
248              
249              
250             sub rollback {
251 28     28   20816 my ($self) = @_;
252            
253 28 100       123 croak 'Transaction already closed' unless $self->is_open;
254 15 100       93 croak 'Explicit rollback of a managed transaction' if $self->{managed};
255            
256 14 100       114 $self->{net}->_request($self, 'DELETE') if $self->{transaction_endpoint};
257 13         96 $self->{closed} = 1;
258             }
259              
260              
261             sub is_open {
262 313     313   3524 my ($self) = @_;
263            
264 313 100       1385 return 0 if $self->{closed};
265 292 100       1183 return 1 if $self->{unused};
266 36         201 return $self->{net}->_is_active_tx($self);
267             }
268              
269              
270             1;
271              
272             __END__