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   80434 use strict;
  1         2  
  1         27  
2 1     1   4 use warnings;
  1         2  
  1         60  
3              
4             package Footprintless::Plugin::Database::AbstractProvider;
5             $Footprintless::Plugin::Database::AbstractProvider::VERSION = '1.01';
6             # ABSTRACT: A base class for database providers
7             # PODNAME: Footprintless::Plugin::Database::AbstractProvider
8              
9 1     1   451 use parent qw(Footprintless::MixableBase);
  1         285  
  1         4  
10              
11 1     1   722 use overload q{""} => 'to_string', fallback => 1;
  1         2  
  1         9  
12              
13 1     1   75 use Carp;
  1         1  
  1         49  
14 1     1   1679 use DBI;
  1         14585  
  1         76  
15 1         69 use Footprintless::Mixins qw(
16             _entity
17 1     1   752 );
  1         4478  
18 1     1   6 use Log::Any;
  1         2  
  1         5  
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 1756 my ($self) = @_;
79              
80 5 50       21 return if ( $self->{connection} );
81              
82 5         24 $self->_connect_tunnel();
83              
84 5         12 my ( $hostname, $port ) = $self->_hostname_port();
85              
86 5         30 $logger->debugf( 'connecting to %s', $self->to_string() );
87             $self->{connection} = DBI->connect( $self->_connection_string(),
88 5   33     83 $self->{username}, $self->{password}, { RaiseError => 1, AutoCommit => 1 } )
89             || croak("unable to connect to $hostname on port $port: $@");
90 5         175859 $logger->tracef('connected');
91              
92 5         68 return $self;
93             }
94              
95             sub _connect_tunnel {
96 5     5   7 my ($self) = @_;
97              
98 5 50       14 return if ( $self->{tunnel} );
99              
100 5 50       13 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         8 return $self;
111             }
112              
113             sub _connection_string {
114 0     0   0 die("abstract method invocation");
115             }
116              
117             sub DESTROY {
118 7     7   10659 my ($self) = @_;
119 7         21 $self->disconnect();
120             }
121              
122             sub disconnect {
123 12     12 1 46 my ($self) = @_;
124              
125 12 100       36 if ( $self->{connection} ) {
126 5         31 $logger->debugf( 'disconnecting from %s', $self->to_string() );
127 5         133 $self->{connection}->disconnect();
128 5         209 delete( $self->{connection} );
129             }
130              
131 12 50       473 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         125 return $self;
138             }
139              
140             sub execute {
141 1     1 1 11 my ( $self, $query ) = @_;
142              
143 1         2 my $result;
144             $self->_process_sql(
145             $query,
146             sub {
147 1     1   2 $result = $_[1];
148             }
149 1         7 );
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   28 my ($self) = @_;
159              
160 25         25 my ( $hostname, $port );
161 25 50       46 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         33 $hostname = $self->{hostname};
167 25         29 $port = $self->{port};
168             }
169              
170 25 50       73 return ( $hostname eq 'localhost' ? '127.0.0.1' : $hostname, $port );
171             }
172              
173             sub _init {
174 7     7   13 my ( $self, %options ) = @_;
175              
176 7         94 my $entity = $self->_entity( $self->{coordinate} );
177              
178 7         144 $self->{backup} = $entity->{backup};
179 7         12 $self->{database} = $entity->{database};
180 7   50     48 $self->{hostname} = $entity->{hostname} || 'localhost';
181 7         10 $self->{password} = $entity->{password};
182 7         14 $self->{port} = $entity->{port};
183 7         19 $self->{schema} = $entity->{schema};
184 7         13 $self->{tunnel_destination_hostname} = $entity->{tunnel_destination_hostname};
185 7         13 $self->{tunnel_hostname} = $entity->{tunnel_hostname};
186 7         13 $self->{tunnel_username} = $entity->{tunnel_username};
187 7         9 $self->{username} = $entity->{username};
188              
189 7         16 return $self;
190             }
191              
192             sub _process_sql {
193 11     11   16 my ( $self, $query, $statement_handler ) = @_;
194              
195             my ( $sql, $parameters ) =
196             ref($query) eq 'HASH'
197             ? ( $query->{sql}, $query->{parameters} )
198 11 100       49 : ($query);
199 11         14 eval {
200 11 50       37 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         136 my $statement_handle = $self->{connection}->prepare_cached($sql);
210 11 100       21939 &{$statement_handler}(
  11         20268  
211             $statement_handle,
212             ( defined($parameters)
213             ? $statement_handle->execute(@$parameters)
214             : $statement_handle->execute()
215             )
216             );
217 11         145 $statement_handle->finish();
218             };
219 11 50       74 if ($@) {
220 0         0 croak("query failed: $@");
221             }
222             }
223              
224             sub query {
225 8     8 1 40 my ( $self, $query, $result_handler ) = @_;
226              
227             $self->_process_sql(
228             $query,
229             sub {
230 8     8   15 my ( $statement_handle, $execute_result ) = @_;
231              
232 8         56 while ( my @row = $statement_handle->fetchrow_array() ) {
233 16         447 &{$result_handler}(@row);
  16         37  
234             }
235             }
236 8         35 );
237 8         39 return;
238             }
239              
240             sub query_for_list {
241 2     2 1 25 my ( $self, $query, $row_mapper ) = @_;
242              
243 2         4 my @results = ();
244             $self->query(
245             $query,
246             sub {
247 4 50   4   10 if ($row_mapper) {
248 4         16 push( @results, &{$row_mapper}(@_) );
  4         11  
249             }
250             else {
251 0         0 push( @results, \@_ );
252             }
253             }
254 2         10 );
255 2 100       12 return wantarray() ? @results : \@results;
256             }
257              
258             sub query_for_map {
259 4     4 1 41 my ( $self, $query, $row_mapper ) = @_;
260              
261 4         7 my %results = ();
262             $self->query(
263             $query,
264             sub {
265 8 100   8   15 if ($row_mapper) {
266 4         6 my $key_value_pair = &{$row_mapper}(@_);
  4         9  
267 4         30 $results{ $key_value_pair->[0] } = $key_value_pair->[1];
268             }
269             else {
270 4         29 $results{ $_[0] } = \@_;
271             }
272             }
273 4         18 );
274 4 100       30 return wantarray() ? %results : \%results;
275             }
276              
277             sub query_for_scalar {
278 2     2 1 23 my ( $self, $query, $row_mapper ) = @_;
279              
280 2         2 my $result;
281             $self->_process_sql(
282             $query,
283             sub {
284 2     2   5 my ( $statement_handle, $execute_result ) = @_;
285 2 50       14 if ( my @row = $statement_handle->fetchrow_array() ) {
286 2 100       74 if ($row_mapper) {
287 1         5 $result = &$row_mapper(@row);
288             }
289             else {
290 1         3 $result = $row[0];
291             }
292             }
293             }
294 2         12 );
295 2         12 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__