File Coverage

blib/lib/Footprintless/Plugin/Database/AbstractProvider.pm
Criterion Covered Total %
statement 110 159 69.1
branch 24 56 42.8
condition 2 13 15.3
subroutine 25 34 73.5
pod 14 15 93.3
total 175 277 63.1


line stmt bran cond sub pod time code
1 1     1   64716 use strict;
  1         2  
  1         20  
2 1     1   4 use warnings;
  1         1  
  1         42  
3              
4             package Footprintless::Plugin::Database::AbstractProvider;
5             $Footprintless::Plugin::Database::AbstractProvider::VERSION = '1.00';
6             # ABSTRACT: A base class for database providers
7             # PODNAME: Footprintless::Plugin::Database::AbstractProvider
8              
9 1     1   376 use parent qw(Footprintless::MixableBase);
  1         214  
  1         3  
10              
11 1     1   595 use overload q{""} => 'to_string', fallback => 1;
  1         2  
  1         4  
12              
13 1     1   51 use Carp;
  1         1  
  1         39  
14 1     1   1291 use DBI;
  1         11182  
  1         48  
15 1         47 use Footprintless::Mixins qw(
16             _entity
17 1     1   413 );
  1         3349  
18 1     1   5 use Log::Any;
  1         1  
  1         3  
19              
20             my $logger = Log::Any->get_logger();
21              
22             sub backup {
23 0     0 1 0 die("abstract method invocation");
24             }
25              
26             sub begin_transaction {
27 0     0 1 0 my ($self) = @_;
28 0 0       0 croak("not connected") unless ( $self->{connection} );
29              
30 0         0 $self->{connection}->begin_work();
31              
32 0         0 return $self;
33             }
34              
35             sub client {
36 0     0 1 0 my ( $self, %options ) = @_;
37              
38 0         0 my $in_file;
39 0         0 eval {
40 0         0 my $in_handle = delete( $options{in_handle} );
41 0 0       0 if ( $options{in_file} ) {
42 0 0       0 open( $in_file, '<', delete( $options{in_file} ) )
43             || croak("invalid in_file: $!");
44             }
45 0 0       0 if ( $options{in_string} ) {
46 0         0 my $string = delete( $options{in_string} );
47 0 0       0 open( $in_file, '<', \$string )
48             || croak("invalid in_string: $!");
49             }
50 0         0 $self->_connect_tunnel();
51              
52 0   0     0 my $local_in = $in_handle || $in_file;
53 0 0       0 local (*STDIN) = $local_in if ($local_in);
54              
55 0         0 require Footprintless::Plugin::Database::SqlShellAdapter;
56             Footprintless::Plugin::Database::SqlShellAdapter::sql_shell( $self->_connection_string(),
57 0         0 $self->{username}, $self->{password}, @{ $options{client_options} } );
  0         0  
58             };
59 0         0 my $error = $@;
60 0         0 $self->disconnect();
61 0 0       0 if ($in_file) {
62 0         0 close($in_file);
63             }
64              
65 0 0       0 croak($error) if ($error);
66             }
67              
68             sub commit_transaction {
69 0     0 1 0 my ($self) = @_;
70 0 0       0 croak("not connected") unless ( $self->{connection} );
71              
72 0         0 $self->{connection}->commit();
73              
74 0         0 return $self;
75             }
76              
77             sub connect {
78 5     5 1 1235 my ($self) = @_;
79              
80 5 50       14 return if ( $self->{connection} );
81              
82 5         14 $self->_connect_tunnel();
83              
84 5         13 my ( $hostname, $port ) = $self->_hostname_port();
85              
86 5         17 $logger->debugf( 'connecting to %s', $self->to_string() );
87             $self->{connection} = DBI->connect( $self->_connection_string(),
88 5   33     68 $self->{username}, $self->{password}, { RaiseError => 1, AutoCommit => 1 } )
89             || croak("unable to connect to $hostname on port $port: $@");
90 5         124143 $logger->tracef('connected');
91              
92 5         48 return $self;
93             }
94              
95             sub _connect_tunnel {
96 5     5   5 my ($self) = @_;
97              
98 5 50       13 return if ( $self->{tunnel} );
99              
100 5 50       8 if ( $self->{tunnel_hostname} ) {
101 0         0 $logger->debugf( 'opening tunnel through %s', $self->{tunnel_hostname} );
102             $self->{tunnel} = $self->{factory}->tunnel(
103             $self->{coordinate},
104             destination_hostname => $self->{tunnel_destination_hostname} || $self->{hostname},
105             destination_port => $self->{port}
106 0   0     0 );
107 0         0 $self->{tunnel}->open();
108             }
109              
110 5         4 return $self;
111             }
112              
113             sub _connection_string {
114 0     0   0 die("abstract method invocation");
115             }
116              
117             sub DESTROY {
118 7     7   8402 my ($self) = @_;
119 7         15 $self->disconnect();
120             }
121              
122             sub disconnect {
123 12     12 1 35 my ($self) = @_;
124              
125 12 100       28 if ( $self->{connection} ) {
126 5         29 $logger->debugf( 'disconnecting from %s', $self->to_string() );
127 5         100 $self->{connection}->disconnect();
128 5         148 delete( $self->{connection} );
129             }
130              
131 12 50       370 if ( $self->{tunnel} ) {
132 0         0 $logger->debug('closing tunnel');
133 0         0 $self->{tunnel}->close();
134 0         0 delete( $self->{tunnel} );
135             }
136              
137 12         83 return $self;
138             }
139              
140             sub execute {
141 1     1 1 9 my ( $self, $query ) = @_;
142              
143 1         1 my $result;
144             $self->_process_sql(
145             $query,
146             sub {
147 1     1   2 $result = $_[1];
148             }
149 1         5 );
150 1         5 return $result;
151             }
152              
153             sub get_schema {
154 0     0 1 0 return $_[0]->{schema};
155             }
156              
157             sub _hostname_port {
158 25     25   16 my ($self) = @_;
159              
160 25         15 my ( $hostname, $port );
161 25 50       34 if ( $self->{tunnel} ) {
162 0   0     0 $hostname = $self->{tunnel}->get_local_hostname() || 'localhost';
163 0         0 $port = $self->{tunnel}->get_local_port();
164             }
165             else {
166 25         27 $hostname = $self->{hostname};
167 25         19 $port = $self->{port};
168             }
169              
170 25 50       61 return ( $hostname eq 'localhost' ? '127.0.0.1' : $hostname, $port );
171             }
172              
173             sub _init {
174 7     7   21 my ( $self, %options ) = @_;
175              
176 7         68 my $entity = $self->_entity( $self->{coordinate} );
177              
178 7         112 $self->{backup} = $entity->{backup};
179 7         8 $self->{database} = $entity->{database};
180 7   50     36 $self->{hostname} = $entity->{hostname} || 'localhost';
181 7         9 $self->{password} = $entity->{password};
182 7         7 $self->{port} = $entity->{port};
183 7         13 $self->{schema} = $entity->{schema};
184 7         8 $self->{tunnel_destination_hostname} = $entity->{tunnel_destination_hostname};
185 7         11 $self->{tunnel_hostname} = $entity->{tunnel_hostname};
186 7         8 $self->{tunnel_username} = $entity->{tunnel_username};
187 7         9 $self->{username} = $entity->{username};
188              
189 7         14 return $self;
190             }
191              
192             sub _process_sql {
193 11     11   13 my ( $self, $query, $statement_handler ) = @_;
194              
195             my ( $sql, $parameters ) =
196             ref($query) eq 'HASH'
197             ? ( $query->{sql}, $query->{parameters} )
198 11 100       28 : ($query);
199 11         13 eval {
200 11 50       28 if ( $logger->is_trace() ) {
201 0 0       0 $logger->trace(
202             "$self->{hostname}: '$sql'",
203             ( $parameters
204             ? ( ",[" . join( ',', @$parameters ) . "]" )
205             : ''
206             )
207             );
208             }
209 11         103 my $statement_handle = $self->{connection}->prepare_cached($sql);
210 11 100       16967 &{$statement_handler}(
  11         15017  
211             $statement_handle,
212             ( defined($parameters)
213             ? $statement_handle->execute(@$parameters)
214             : $statement_handle->execute()
215             )
216             );
217 11         115 $statement_handle->finish();
218             };
219 11 50       57 if ($@) {
220 0         0 croak("query failed: $@");
221             }
222             }
223              
224             sub query {
225 8     8 1 33 my ( $self, $query, $result_handler ) = @_;
226              
227             $self->_process_sql(
228             $query,
229             sub {
230 8     8   47 my ( $statement_handle, $execute_result ) = @_;
231              
232 8         49 while ( my @row = $statement_handle->fetchrow_array() ) {
233 16         346 &{$result_handler}(@row);
  16         27  
234             }
235             }
236 8         28 );
237 8         25 return;
238             }
239              
240             sub query_for_list {
241 2     2 1 21 my ( $self, $query, $row_mapper ) = @_;
242              
243 2         3 my @results = ();
244             $self->query(
245             $query,
246             sub {
247 4 50   4   7 if ($row_mapper) {
248 4         5 push( @results, &{$row_mapper}(@_) );
  4         8  
249             }
250             else {
251 0         0 push( @results, \@_ );
252             }
253             }
254 2         8 );
255 2 100       11 return wantarray() ? @results : \@results;
256             }
257              
258             sub query_for_map {
259 4     4 1 30 my ( $self, $query, $row_mapper ) = @_;
260              
261 4         5 my %results = ();
262             $self->query(
263             $query,
264             sub {
265 8 100   8   13 if ($row_mapper) {
266 4         4 my $key_value_pair = &{$row_mapper}(@_);
  4         7  
267 4         26 $results{ $key_value_pair->[0] } = $key_value_pair->[1];
268             }
269             else {
270 4         16 $results{ $_[0] } = \@_;
271             }
272             }
273 4         13 );
274 4 100       22 return wantarray() ? %results : \%results;
275             }
276              
277             sub query_for_scalar {
278 2     2 1 15 my ( $self, $query, $row_mapper ) = @_;
279              
280 2         3 my $result;
281             $self->_process_sql(
282             $query,
283             sub {
284 2     2   3 my ( $statement_handle, $execute_result ) = @_;
285 2 50       13 if ( my @row = $statement_handle->fetchrow_array() ) {
286 2 100       53 if ($row_mapper) {
287 1         4 $result = &$row_mapper(@row);
288             }
289             else {
290 1         2 $result = $row[0];
291             }
292             }
293             }
294 2         8 );
295 2         10 return $result;
296             }
297              
298             sub restore {
299 0     0 1   die("abstract method invocation");
300             }
301              
302             sub rollback_transaction {
303 0     0 1   my ($self) = @_;
304 0 0         croak("not connected") unless ( $self->{connection} );
305              
306 0           $self->{connection}->rollback();
307              
308 0           return $self;
309             }
310              
311             sub to_string {
312 0     0 0   my ($self) = @_;
313 0           return "{schema=>'$self->{schema}',hostname=>'$self->{hostname}',port=>$self->{port}}";
314             }
315              
316             1;
317              
318             __END__