File Coverage

blib/lib/Footprintless/Plugin/Database/AbstractProvider.pm
Criterion Covered Total %
statement 117 184 63.5
branch 32 92 34.7
condition 3 19 15.7
subroutine 25 35 71.4
pod 14 15 93.3
total 191 345 55.3


line stmt bran cond sub pod time code
1 1     1   97855 use strict;
  1         10  
  1         33  
2 1     1   5 use warnings;
  1         1  
  1         40  
3              
4             package Footprintless::Plugin::Database::AbstractProvider;
5             $Footprintless::Plugin::Database::AbstractProvider::VERSION = '1.06';
6             # ABSTRACT: A base class for database providers
7             # PODNAME: Footprintless::Plugin::Database::AbstractProvider
8              
9 1     1   335 use parent qw(Footprintless::MixableBase);
  1         214  
  1         4  
10              
11 1     1   639 use overload q{""} => 'to_string', fallback => 1;
  1         2  
  1         5  
12              
13 1     1   57 use Carp;
  1         2  
  1         37  
14 1     1   1421 use DBI;
  1         13755  
  1         49  
15 1         50 use Footprintless::Mixins qw(
16             _entity
17 1     1   412 );
  1         4328  
18 1     1   7 use Log::Any;
  1         1  
  1         4  
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 _column_info {
69 0     0   0 my ( $self, $statement_handle ) = @_;
70 0         0 my $column_info = [];
71 0 0       0 if ( defined( $statement_handle->{NUM_OF_FIELDS} ) ) {
72             $column_info = [
73             map {
74 0         0 my $element = {};
75             $element->{name} = $statement_handle->{NAME}->[$_]
76 0 0       0 if defined( $statement_handle->{NAME} );
77             $element->{type} = $statement_handle->{TYPE}->[$_]
78 0 0       0 if defined( $statement_handle->{TYPE} );
79 0 0       0 if ( my $type_info =
80             $self->{connection}->type_info( $statement_handle->{TYPE}->[$_] ) )
81             {
82             $element->{type_name} = $type_info->{TYPE_NAME}
83 0 0       0 if defined( $type_info->{TYPE_NAME} );
84 0 0       0 $element->{type_info} = $type_info if defined( $type_info->{NAME} );
85             }
86             $element->{column_size} = $statement_handle->{PRECISION}->[$_]
87 0 0       0 if defined( $statement_handle->{PRECISION} );
88             $element->{scale} = $statement_handle->{SCALE}->[$_]
89 0 0       0 if defined( $statement_handle->{SCALE} );
90 0 0 0     0 if ( defined( $statement_handle->{NULLABLE} )
91             && ( my $nullable = $statement_handle->{NULLABLE}->[$_] ) != 2 )
92             {
93 0 0       0 $element->{nullable} = $nullable ? 1 : 0;
94             }
95 0         0 $element;
96 0         0 } 0 .. $statement_handle->{NUM_OF_FIELDS} - 1
97             ];
98             }
99 0         0 return $column_info;
100             }
101              
102             sub commit_transaction {
103 0     0 1 0 my ($self) = @_;
104 0 0       0 croak("not connected") unless ( $self->{connection} );
105              
106 0         0 $self->{connection}->commit();
107              
108 0         0 return $self;
109             }
110              
111             sub connect {
112 5     5 1 899 my ($self) = @_;
113              
114 5 50       16 return if ( $self->{connection} );
115              
116 5         21 $self->_connect_tunnel();
117              
118 5         9 my ( $hostname, $port ) = $self->_hostname_port();
119              
120 5         18 $logger->debugf( 'connecting to %s', $self->to_string() );
121             $self->{connection} = DBI->connect( $self->_connection_string(),
122 5   33     79 $self->{username}, $self->{password}, { RaiseError => 1, AutoCommit => 1 } )
123             || croak("unable to connect to $hostname on port $port: $@");
124 5         145123 $logger->tracef('connected');
125              
126 5         52 return $self;
127             }
128              
129             sub _connect_tunnel {
130 5     5   9 my ($self) = @_;
131              
132 5 50       12 return if ( $self->{tunnel} );
133              
134 5 50       12 if ( $self->{tunnel_hostname} ) {
135 0         0 $logger->debugf( 'opening tunnel through %s', $self->{tunnel_hostname} );
136             $self->{tunnel} = $self->{factory}->tunnel(
137             $self->{coordinate},
138             destination_hostname => $self->{tunnel_destination_hostname} || $self->{hostname},
139             destination_port => $self->{port}
140 0   0     0 );
141 0         0 $self->{tunnel}->open();
142             }
143              
144 5         10 return $self;
145             }
146              
147             sub _connection_string {
148 0     0   0 die("abstract method invocation");
149             }
150              
151             sub DESTROY {
152 7     7   8585 my ($self) = @_;
153 7         21 $self->disconnect();
154             }
155              
156             sub disconnect {
157 12     12 1 48 my ($self) = @_;
158              
159 12 100       32 if ( $self->{connection} ) {
160 5         19 $logger->debugf( 'disconnecting from %s', $self->to_string() );
161 5         113 $self->{connection}->disconnect();
162 5         194 delete( $self->{connection} );
163             }
164              
165 12 50       494 if ( $self->{tunnel} ) {
166 0         0 $logger->debug('closing tunnel');
167 0         0 $self->{tunnel}->close();
168 0         0 delete( $self->{tunnel} );
169             }
170              
171 12         90 return $self;
172             }
173              
174             sub execute {
175 1     1 1 11 my ( $self, $query ) = @_;
176              
177 1         2 my $result;
178             $self->_process_sql(
179             $query,
180             sub {
181 1     1   3 $result = $_[1];
182             }
183 1         6 );
184 1         4 return $result;
185             }
186              
187             sub get_schema {
188 0     0 1 0 return $_[0]->{schema};
189             }
190              
191             sub _hostname_port {
192 25     25   38 my ($self) = @_;
193              
194 25         35 my ( $hostname, $port );
195 25 50       45 if ( $self->{tunnel} ) {
196 0   0     0 $hostname = $self->{tunnel}->get_local_hostname() || 'localhost';
197 0         0 $port = $self->{tunnel}->get_local_port();
198             }
199             else {
200 25         41 $hostname = $self->{hostname};
201 25         35 $port = $self->{port};
202             }
203              
204 25 50       67 return ( $hostname eq 'localhost' ? '127.0.0.1' : $hostname, $port );
205             }
206              
207             sub _init {
208 7     7   17 my ( $self, %options ) = @_;
209              
210 7         58 my $entity = $self->_entity( $self->{coordinate} );
211              
212 7         160 $self->{backup} = $entity->{backup};
213 7         14 $self->{database} = $entity->{database};
214 7   50     32 $self->{hostname} = $entity->{hostname} || 'localhost';
215 7         38 $self->{password} = $entity->{password};
216 7         14 $self->{port} = $entity->{port};
217 7         19 $self->{schema} = $entity->{schema};
218 7         11 $self->{tunnel_destination_hostname} = $entity->{tunnel_destination_hostname};
219 7         15 $self->{tunnel_hostname} = $entity->{tunnel_hostname};
220 7         12 $self->{tunnel_username} = $entity->{tunnel_username};
221 7         13 $self->{username} = $entity->{username};
222              
223 7         14 return $self;
224             }
225              
226             sub _process_sql {
227 11     11   20 my ( $self, $query, $statement_handler ) = @_;
228              
229             my ( $sql, $parameters ) =
230             ref($query) eq 'HASH'
231             ? ( $query->{sql}, $query->{parameters} )
232 11 100       35 : ($query);
233 11         14 eval {
234 11 50       35 if ( $logger->is_trace() ) {
235 0 0       0 $logger->trace(
236             "$self->{hostname}: '$sql'",
237             ( $parameters
238             ? ( ",[" . join( ',', @$parameters ) . "]" )
239             : ''
240             )
241             );
242             }
243 11         117 my $statement_handle = $self->{connection}->prepare_cached($sql);
244 11 100       22908 &{$statement_handler}(
  11         19501  
245             $statement_handle,
246             ( defined($parameters)
247             ? $statement_handle->execute(@$parameters)
248             : $statement_handle->execute()
249             )
250             );
251 11         158 $statement_handle->finish();
252             };
253 11 50       78 if ($@) {
254 0         0 croak("query failed: $@");
255             }
256             }
257              
258             sub query {
259 8     8 1 40 my ( $self, $query, $result_handler, %options ) = @_;
260 8         17 my $hash = $options{hash};
261 8   33     32 my $column_info = $options{column_info} || ( $hash && [] );
262              
263             $self->_process_sql(
264             $query,
265             sub {
266 8     8   16 my ( $statement_handle, $execute_result ) = @_;
267 8 50       19 @$column_info = @{ $self->_column_info($statement_handle) } if $column_info;
  0         0  
268 8 50       21 if ( !$options{no_fetch} ) {
269 8         45 while ( my @row = $statement_handle->fetchrow_array() ) {
270             @row =
271 16 50       478 map { $column_info->[$_]->{name} => $row[$_] } 0 .. ( scalar(@row) - 1 )
  0         0  
272             if $hash;
273 16         22 &{$result_handler}(@row);
  16         27  
274             }
275             }
276             }
277 8         48 );
278 8         47 return;
279             }
280              
281             sub query_for_list {
282 2 50   2 1 24 my ( $self, $query, $row_mapper, %options ) =
283             ( shift, shift, ( scalar(@_) % 2 ) ? shift : undef, @_ );
284              
285 2         5 my @results = ();
286 2         4 my $hash = $options{hash};
287             $self->query(
288             $query,
289             sub {
290 4 50   4   9 if ($row_mapper) {
291 4         8 push( @results, &{$row_mapper}(@_) );
  4         7  
292             }
293             else {
294 0 0       0 push( @results, $hash ? {@_} : \@_ );
295             }
296             },
297 2         11 %options
298             );
299 2 100       11 return wantarray() ? @results : \@results;
300             }
301              
302             sub query_for_map {
303 4 100   4 1 39 my ( $self, $query, $row_mapper, %options ) =
304             ( shift, shift, ( scalar(@_) % 2 ) ? shift : undef, @_ );
305              
306 4         7 my %results = ();
307 4         7 my $hash = $options{hash};
308             $self->query(
309             $query,
310             sub {
311 8 100   8   16 if ($row_mapper) {
312 4         6 my $key_value_pair = &{$row_mapper}(@_);
  4         9  
313 4         27 $results{ $key_value_pair->[0] } = $key_value_pair->[1];
314             }
315             else {
316 4 50       22 $results{ $_[ $hash ? 1 : 0 ] } = $hash ? {@_} : \@_;
    50          
317             }
318             },
319 4         18 %options
320             );
321 4 100       26 return wantarray() ? %results : \%results;
322             }
323              
324             sub query_for_scalar {
325 2     2 1 18 my ( $self, $query, $row_mapper ) = @_;
326              
327 2         4 my $result;
328             $self->_process_sql(
329             $query,
330             sub {
331 2     2   6 my ( $statement_handle, $execute_result ) = @_;
332 2 50       11 if ( my @row = $statement_handle->fetchrow_array() ) {
333 2 100       73 if ($row_mapper) {
334 1         4 $result = &$row_mapper(@row);
335             }
336             else {
337 1         2 $result = $row[0];
338             }
339             }
340             }
341 2         11 );
342 2         28 return $result;
343             }
344              
345             sub restore {
346 0     0 1   die("abstract method invocation");
347             }
348              
349             sub rollback_transaction {
350 0     0 1   my ($self) = @_;
351 0 0         croak("not connected") unless ( $self->{connection} );
352              
353 0           $self->{connection}->rollback();
354              
355 0           return $self;
356             }
357              
358             sub to_string {
359 0     0 0   my ($self) = @_;
360 0           return "{schema=>'$self->{schema}',hostname=>'$self->{hostname}',port=>$self->{port}}";
361             }
362              
363             1;
364              
365             __END__