File Coverage

blib/lib/Mojar/Mysql/Connector.pm
Criterion Covered Total %
statement 54 226 23.8
branch 18 102 17.6
condition 13 101 12.8
subroutine 11 45 24.4
pod 4 5 80.0
total 100 479 20.8


line stmt bran cond sub pod time code
1             package Mojar::Mysql::Connector;
2 3     3   74940 use DBI 1.4.3;
  3         61780  
  3         300  
3 3     3   38 use Mojo::Base 'DBI';
  3         5  
  3         29  
4              
5             # Register subclass structure
6             __PACKAGE__->init_rootclass;
7              
8             our $VERSION = 2.113;
9              
10 3     3   4183 use File::Spec::Functions 'catfile';
  3         3039  
  3         302  
11 3     3   3026 use Mojar::ClassShare 'have';
  3         1849  
  3         23  
12              
13             sub import {
14 3     3   33 my ($pkg, %param) = @_;
15 3         9 my $caller = caller;
16             # Helpers
17 3 100 50     31 $param{-connector} //= 1 if exists $param{-dbh} and $param{-dbh};
      66        
18 3 100 66     21 if (exists $param{-connector} and my $cname = delete $param{-connector}) {
19 1 50       5 $cname = 'connector' if "$cname" eq '1';
20 3     3   408 no strict 'refs';
  3         7  
  3         5190  
21 1         8 *{"${caller}::$cname"} = sub {
22 1     1   2242 my $self = shift;
23 1 50       8 if (@_) {
24 0 0       0 $self->{$cname} = (@_ > 1) ? Mojar::Mysql::Connector->new(@_) : shift;
25 0         0 return $self;
26             }
27 1   33     17 return $self->{$cname} //= Mojar::Mysql::Connector->new;
28 1         5 };
29 1 50 33     7 if (exists $param{-dbh} and my $hname = delete $param{-dbh}) {
30 1 50       4 $hname = 'dbh' if "$hname" eq '1';
31 1         5 *{"${caller}::$hname"} = sub {
32 0     0   0 my $self = shift;
33 0 0       0 if (@_) {
34 0 0       0 $self->{$hname} = (@_ > 1) ? $self->$cname->connect(@_) : shift;
35 0         0 return $self;
36             }
37 0 0 0     0 return $self->{$hname}
38             if defined $self->{$hname} and $self->{$hname}->ping;
39 0         0 return $self->{$hname} = $self->$cname->connect;
40 1         4 };
41             }
42             }
43             # Global defaults
44 3 50 66     15 if (%param and %{$pkg->Defaults}) {
  2         55  
45             # Already have defaults => check unchanged
46             # Not interested in defaults of Defaults => use hash not methods
47 0   0     0 my $ps = join ':', map +($_ .':'. ($param{$_} // 'undef')),
48             sort keys %param;
49 0         0 my $ds = join ':', map +($_ .':'. ($pkg->Defaults->{$_} // 'undef')),
50 0   0     0 sort keys %{$pkg->Defaults};
51 0 0       0 die "Redefining class defaults for $pkg" unless $ps eq $ds;
52             }
53 3 100       30 @{$pkg->Defaults}{keys %param} = values %param if %param;
  2         39  
54             # Debugging
55 3 50 33     5399 $pkg->trace($param{TraceLevel})
56             if exists $param{TraceLevel} and defined $param{TraceLevel};
57             }
58              
59             # Class attribute
60              
61             # Use a singleton object for holding use-time class defaults
62             have Defaults => sub { bless {} => ref $_[0] || $_[0] };
63              
64             # Attributes
65              
66             my @DbdFields = qw( RaiseError PrintError PrintWarn AutoCommit TraceLevel
67             mysql_enable_utf8 mysql_auto_reconnect );
68              
69             has RaiseError => 1;
70             has PrintError => 0;
71             has PrintWarn => 0;
72             has AutoCommit => 1;
73             has TraceLevel => 0;
74             has mysql_enable_utf8 => 1;
75             has mysql_auto_reconnect => 0;
76              
77             my @ConFields = qw( label cnfdir cnf cnfgroup );
78              
79             has 'label';
80             has cnfdir => '.';
81             has 'cnf';
82             has 'cnfgroup';
83              
84             my @DbiFields = qw( driver host port schema user password );
85              
86             has driver => 'mysql';
87             has 'host'; # eg 'localhost'
88             has 'port'; # eg 3306
89             has 'schema'; # eg 'test';
90             has 'user';
91             has 'password';
92              
93             # Private function
94              
95 0     0 0 0 sub croak { require Carp; goto &Carp::croak; }
  0         0  
96              
97             # Public methods
98              
99             sub new {
100 5     5 1 8808 my ($proto, %param) = @_;
101             # $proto may contain defaults to be cloned
102             # %param may contain defaults for overriding
103 3         82 my %defaults = ref $proto ? ( %{ ref($proto)->Defaults }, %$proto )
  2         65  
104 5 100       15 : %{ $proto->Defaults };
105 5         88 return Mojo::Base::new($proto, %defaults, %param);
106             }
107              
108             sub connect {
109 0     0 1 0 my ($proto, @args) = @_;
110 0   0     0 my $class = ref $proto || $proto;
111 0 0 0     0 @args = $proto->dsn(@args) unless @args and $args[0] =~ /^DBI:/i;
112 0         0 my $dbh;
113             eval {
114 0         0 $dbh = $class->SUPER::connect(@args)
115             }
116 0 0       0 or do {
117 0         0 my $e = $@;
118 0         0 croak sprintf "Connection error\n%s\n%s",
119             $proto->dsn_to_dump(@args), $e;
120             };
121 0         0 return $dbh;
122             }
123              
124             sub dsn {
125 2     2 1 4370 my ($proto, %param) = @_;
126 2         7 my $param = $proto->new(%param);
127              
128 2         20 my $cnf_txt = '';
129 2 50       38 if (my $cnf = $param->cnf) {
130             # MySQL .cnf file
131 0 0       0 $cnf .= '.cnf' unless $cnf =~ /\.cnf$/;
132 0 0 0     0 $cnf = catfile $param->cnfdir, $cnf if ! -r $cnf and defined $param->cnfdir;
133 0 0 0     0 croak "Failed to find/read .cnf file ($cnf)" unless -f $cnf and -r $cnf;
134              
135 0         0 $cnf_txt = ';mysql_read_default_file='. $cnf;
136 0 0       0 $cnf_txt .= ';mysql_read_default_group='. $param->cnfgroup
137             if defined $param->cnfgroup;
138             }
139              
140             # DBD params
141             # Only set private_config if it would have useful values
142 2         14 my %custom;
143 2   33     40 defined($param->$_) and $custom{$_} = $param->$_ for qw(label cnf cnfgroup);
144 2 50       106 my $dbd_param = %custom ? { private_config => {%custom} } : {};
145 2         40 @$dbd_param{@DbdFields} = map $param->$_, @DbdFields;
146              
147             return (
148 2 50 33     325 'DBI:'. $param->driver .q{:}
    50 50        
149             . ($param->schema // $param->{db} // '')
150             . (defined $param->host ? q{;host=}. $param->host : '')
151             . (defined $param->port ? q{;port=}. $param->port : '')
152             . $cnf_txt,
153             $param->user,
154             $param->password,
155             $dbd_param
156             );
157             }
158              
159             sub dsn_to_dump {
160 0     0 1   my ($proto, @args) = @_;
161 0 0         @args = $proto->dsn unless @args;
162             # Occlude password
163 0 0 0       if ($args[2] and $_ = length $args[2] and $_ > 1) {
      0        
164 0           --$_;
165 0           my $blanks = '*' x $_;
166 0           $args[2] = substr($args[2], 0, 1). $blanks;
167             }
168 0           require Mojar::Util;
169 0           return Mojar::Util::dumper(@args);
170             }
171              
172             # ============
173             package Mojar::Mysql::Connector::db;
174             @Mojar::Mysql::Connector::db::ISA = 'DBI::db';
175              
176 3     3   2880 use Mojar::Util 'lc_keys';
  3         23363  
  3         233  
177 3     3   29 use Scalar::Util 'looks_like_number';
  3         5  
  3         8539  
178              
179             # Private functions
180              
181 0     0     sub croak { require Carp; goto &Carp::croak; }
  0            
182              
183             our $_as_hash = { Slice => {} };
184 0     0     sub as_hash { $_as_hash }
185              
186             # Public methods
187              
188 0     0     sub dsn { shift->get_info(2) }
189             # 2 : SQL_DATA_SOURCE_NAME
190              
191 0     0     sub mysqld_version { shift->get_info(18) }
192             # 18 : SQL_DBMS_VER
193              
194 0     0     sub identifier_quote { shift->get_info(29) }
195             # 29 : SQL_IDENTIFIER_QUOTE_CHAR
196              
197 0     0     sub identifier_separator { shift->get_info(41) }
198             # 41 : SQL_QUALIFIER_NAME_SEPARATOR
199              
200 0     0     sub async_mode { shift->get_info(10021) }
201             # 10021 : SQL_ASYNC_MODE
202              
203 0     0     sub async_max_statements { shift->get_info(10022) }
204             # 10022 : SQL_MAX_ASYNC_CONCURRENT_STATEMENTS
205              
206 0   0 0     sub thread_id { shift->{mysql_thread_id} // 0 }
207              
208             sub current_schema {
209 0     0     my ($self) = @_;
210 0           my ($schema) = $self->selectrow_array(
211             q{SELECT DATABASE()}
212             );
213 0           return $schema;
214             }
215              
216 0     0     sub session_var { shift->_var('SESSION', @_) }
217              
218             sub global_var {
219 0     0     my $self = shift;
220 0 0 0       return $self->_var('GLOBAL', @_)
      0        
221             if @_ >= 2 or @_ == 1 and $_[0] ne 'have_innodb';
222              
223 0           my $variables = $self->_var('GLOBAL');
224              
225             # Workaround for MySQL bug #59393 wrt ignore-builtin-innodb
226 0 0 0       $variables->{have_innodb} = 'NO'
      0        
227             if exists $variables->{ignore_builtin_innodb}
228             and ($variables->{ignore_builtin_innodb} // '') eq 'ON';
229              
230 0 0 0       return $variables->{have_innodb} if @_ == 1 and $_[0] eq 'have_innodb';
231 0           return $variables;
232             }
233              
234 0     0     sub disable_quotes { shift->session_var( sql_quote_show_create => 0 ) }
235              
236             sub enable_quotes {
237 0     0     my ($self, $value) = @_;
238 0   0       $value //= 1;
239 0           $self->session_var( sql_quote_show_create => $value )
240             }
241              
242 0     0     sub disable_fk_checks { shift->session_var( foreign_key_checks => 0 ) }
243              
244             sub enable_fk_checks {
245 0     0     my ($self, $value) = @_;
246 0   0       $value //= 1;
247 0           $self->session_var( foreign_key_checks => $value )
248             }
249              
250             sub schemata {
251 0     0     my ($self, @args) = @_;
252             # args[0] : schema pattern
253 0           my $schemata;
254             eval {
255 0           my $sql = q{SHOW DATABASES};
256 0 0         $sql .= sprintf q{ LIKE '%s'}, $args[0] if defined $args[0];
257 0 0         $schemata = $self->selectcol_arrayref($sql, $args[1]) or die;
258 0           @$schemata = grep !/^lost\+found/, @$schemata;
259 0           1;
260             }
261 0 0         or do {
262 0   0       my $e = $@ // '';
263 0           croak "Failed to list schemata\n$e";
264             };
265 0           return $schemata;
266             }
267              
268             sub tables_and_views {
269 0     0     my ($self, @args) = @_;
270             # args[0] : schema
271             # args[1] : table pattern
272             # args[2] : type
273             # args[3] : attr
274 0   0       $args[2] //= 'TABLE,VIEW';
275 0           my $tables;
276             eval {
277 0           my $sth = $self->table_info('', @args);
278 0           @$tables = map $_->[2], @{$sth->fetchall_arrayref};
  0            
279 0           1;
280             }
281 0 0         or do {
282 0   0       my $e = $@ // '';
283 0           croak "Failed to list tables\n$e";
284             };
285 0           return $tables;
286             }
287              
288             sub real_tables {
289 0     0     my ($self, @args) = @_;
290             # args[0] : schema
291             # args[1] : table pattern
292             # args[2] : attr
293 0           return $self->tables_and_views(@args[0,1], 'TABLE', $args[2]);
294             }
295              
296             sub views {
297 0     0     my ($self, @args) = @_;
298             # args[0] : schema
299             # args[1] : table pattern
300             # args[2] : attr
301 0           return $self->tables_and_views(@args[0,1], 'VIEW', $args[2]);
302             }
303              
304             sub selectall_arrayref_hashrefs {
305 0     0     my ($self, $sql, $opts, @args) = @_;
306 0 0         if (defined $opts) {
307 0           $opts->{Slice} = {};
308             }
309             else {
310 0           $opts = $_as_hash;
311             }
312 0           return $self->selectall_arrayref($sql, $opts, @args);
313             }
314              
315             sub processes {
316 0     0     my $p = shift->selectall_arrayref_hashrefs(q{SHOW FULL PROCESSLIST});
317 0           @$p = map lc_keys($_), @$p;
318 0           return $p;
319             }
320              
321             sub engines {
322 0     0     my ($self) = @_;
323              
324 0           my $engines = {};
325 0           my $e = $self->selectall_arrayref(q{SHOW ENGINES});
326 0           for (@$e) {
327 0 0         if ($_->[1] eq 'DEFAULT') {
328 0           $engines->{default} = lc $_->[0];
329 0           $engines->{lc $_->[0]} = 1;
330             }
331             else {
332 0 0         $engines->{lc $_->[0]} = $_->[1] eq 'YES' ? 1 : 0;
333             }
334             }
335 0           return $engines;
336             }
337              
338             sub statistics {
339 0     0     my ($self) = @_;
340              
341             # Arbitrary query to ensure results
342 0           ($_) = $self->selectrow_array(q{SELECT VERSION()});
343              
344 0           my $s = $self->selectall_arrayref(q{SHOW /*!50000 GLOBAL */ STATUS});
345 0           return lc_keys { map @$_, @$s };
346             }
347              
348             sub indices {
349 0     0     my ($self, $schema, $table) = @_;
350 0 0 0       croak 'Missing required schema name' unless defined $schema and length $schema;
351 0 0 0       croak 'Missing required table name' unless defined $table and length $table;
352 0           my $i = $self->selectall_arrayref(sprintf(
353             q{SHOW INDEXES FROM %s IN %s}, $table, $schema
354             ), $_as_hash
355             );
356             # $i is arrayref of hashrefs
357 0           lc_keys $_ for @$i;
358 0           return $i;
359             }
360              
361             sub table_status {
362 0     0     my ($self, $schema, $table_pattern) = @_;
363 0 0 0       croak 'Missing required schema name' unless defined $schema and length $schema;
364 0           my $sql = sprintf
365             q{SHOW TABLE STATUS FROM %s}, $schema;
366 0 0         $sql .= sprintf(q{ LIKE '%s'}, $table_pattern) if defined $table_pattern;
367 0           my $s = $self->selectall_arrayref($sql, $_as_hash);
368             # $s is arrayref of hashrefs
369 0           lc_keys $_ for @$s;
370 0           return $s;
371             }
372              
373             sub engine_status {
374 0     0     my ($self, $engine) = @_;
375 0   0       $engine //= 'InnoDB';
376              
377 0           my ($raw) = $self->selectrow_array(
378             q{SHOW INNODB STATUS}
379             );
380              
381 0           my ($title, $buffer) = ('', '');
382 0           my $status = {};
383 0           for (split /^/, $raw) {
384 0 0 0       if (/^\-+$/ and length $buffer) {
    0          
    0          
385             # Finish previous record
386 0           $status->{$title} = $buffer;
387 0           $title = $buffer = '';
388             }
389             elsif (/^-+$/) {
390             # Start new record
391             }
392             elsif (not length $title) {
393 0           chomp;
394 0           $title = lc $_;
395 0           $title =~ s/\s/_/g;
396 0           $title =~ s/\W//g;
397             }
398             else {
399 0           $buffer .= $_;
400             }
401             # Ignore final record
402             }
403 0           return $status;
404             }
405              
406             sub table_space {
407 0     0     my ($self, $schema, $table) = @_;
408 0           my $space;
409             eval {
410 0           ($space) = $self->selectrow_array(
411             q{SELECT CONCAT(TRUNCATE(DATA_FREE / 1024, 0), ' kB')
412             FROM information_schema.TABLES
413             WHERE
414             TABLE_SCHEMA = ?
415             AND TABLE_NAME = ?},
416             undef,
417             $schema, $table
418             );
419 0           $space ne '0 kB';
420             }
421 0 0         or eval {
422 0           my $comment = $self->table_status($schema, $table)->[0]{comment};
423 0 0         $space = $1 if $comment =~ /InnoDB free: (\d+ \w+)/;
424             };
425 0           return $space;
426             }
427              
428             sub date_from_today {
429 0     0     my ($self, $days, $format) = @_;
430 0   0       $days //= 0;
431 0   0       $format //= '%Y-%m-%d';
432 0           my ($date) = $self->selectrow_array(sprintf
433             q{SELECT DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL %s DAY), '%s')},
434             $days, $format
435             );
436 0           return $date;
437             }
438              
439             # Private method
440              
441             sub _var {
442 0     0     my ($self, $scope) = (shift, shift);
443 0   0       $scope //= 'SESSION';
444              
445 0 0         unless (@_) {
446             # All vars
447 0           my $v = $self->selectall_arrayref(qq{SHOW $scope VARIABLES});
448 0           return { map @$_, @$v };
449             }
450              
451 0           my $var = shift;
452 0 0         unless (@_) {
453             # Getter
454 0           my ($value) = $self->selectrow_array(sprintf
455             q{SELECT @@%s.%s}, $scope, $var);
456 0           return $value;
457             }
458              
459             # Setter
460 0           my $value = shift;
461 0           my ($old, $new);
462             eval {
463 0           ($old) = $self->selectrow_array(sprintf
464             q{SELECT @@%s.%s}, $scope, $var);
465 0 0         $value = "'$value'" unless looks_like_number $value;
466 0           $self->do(qq{SET $scope $var = $value});
467 0           ($new) = $self->selectrow_array(sprintf
468             q{SELECT @@%s.%s}, $scope, $var);
469 0           1;
470             }
471 0 0         or do {
472 0   0       my $e = $@ // '';
473 0           croak "Failed to set var ($var)\n$e";
474             };
475 0 0         return wantarray ? ($old, $new) : $self;
476             }
477              
478             #TODO: clean up this ancient code
479             #sub insert_hash {
480             # my ($self, $schema, $table, $field_map) = @_;
481             # my @fields = keys %$field_map;
482             # my @values = values %$field_map;
483             # $self->do(sprintf(
484             #q{INSERT INTO %s.%s (%s) VALUES (%s)},
485             # $schema,
486             # $table,
487             # join(q{,}, @fields),
488             # join(q{,}, '?' x @fields)),
489             # undef,
490             # @values
491             # );
492             #}
493              
494             #TODO: clean up this ancient code
495             #sub search_hash {
496             # my ($self, $schema, $table, $field_map, @columns) = @_;
497             # my @fields = keys %$field_map;
498             # my @values = values %$field_map;
499             # my $wanted = scalar(@columns) ? join q{, }, @columns : q{*};
500             # my $where = '';
501             # $where = q{WHERE }. join q{ AND }, map '$_ = ?', @fields if @fields;
502             # $self->selectall_arrayref(sprintf(
503             #q{SELECT %s FROM %s.%s %s},
504             # $wanted, $schema, $table, $where),
505             # undef,
506             # @values
507             # );
508             #}
509              
510             # ============
511             package Mojar::Mysql::Connector::st;
512             @Mojar::Mysql::Connector::st::ISA = 'DBI::st';
513              
514             1;
515             __END__