File Coverage

blib/lib/DBIx/QuickDB/Driver/MySQL.pm
Criterion Covered Total %
statement 62 213 29.1
branch 6 90 6.6
condition 4 33 12.1
subroutine 17 38 44.7
pod 11 24 45.8
total 100 398 25.1


line stmt bran cond sub pod time code
1             package DBIx::QuickDB::Driver::MySQL;
2 13     13   153933 use strict;
  13         39  
  13         434  
3 13     13   47 use warnings;
  13         19  
  13         787  
4              
5             our $VERSION = '0.000040';
6              
7 13     13   2656 use Capture::Tiny qw/capture/;
  13         28266  
  13         669  
8 13     13   60 use Carp qw/confess croak/;
  13         34  
  13         584  
9 13     13   4946 use DBIx::QuickDB::Util qw/strip_hash_defaults/;
  13         40  
  13         184  
10 13     13   487 use IPC::Cmd qw/can_run/;
  13         26  
  13         805  
11 13     13   62 use Scalar::Util qw/reftype blessed/;
  13         22  
  13         735  
12              
13 13     13   108 use parent 'DBIx::QuickDB::Driver';
  13         24  
  13         109  
14 13         2763 use DBIx::QuickDB::Util::HashBase qw{
15            
16              
17             +dbd_driver
18            
19            
20            
21              
22            
23              
24            
25 13     13   868 };
  13         23  
26              
27 0     0 0 0 sub provider { croak "'$_[0]' does not implement provider" }
28 0     0 0 0 sub verify_provider { croak "'$_[0]' does not implement verify_provider" }
29              
30 40     40 0 48 sub dbd_driver_order { shift; grep { $_ } @_, 'DBD::MariaDB', 'DBD::mysql' }
  40         105  
  133         309  
31              
32 16     16 0 32 sub server_bin_list { qw/mysqld/ }
33 0     0 0 0 sub client_bin_list { qw/mysql/ }
34 0     0 0 0 sub install_bin_list { qw/mysql_install_db/ }
35 40     40 0 113 sub server_bin { $_[0]->provider_info->{server_bin} }
36 40     40 0 80 sub client_bin { $_[0]->provider_info->{client_bin} }
37 0     0 0 0 sub install_bin { $_[0]->provider_info->{install_bin} }
38 0     0 0 0 sub error_log { "$_[0]->{+DIR}/error.log" }
39              
40             my %PROVIDER_CACHE;
41             sub provider_info {
42 120     120 0 136 my $this = shift;
43              
44 120   33     306 my $class = blessed($this) || $this;
45 120 100       485 return $PROVIDER_CACHE{$class} if $PROVIDER_CACHE{$class};
46              
47 23         38 my %found;
48              
49 23         31 my $viable = 0;
50 23         69 for my $bin ($this->server_bin_list) {
51 30 50       1665 if (my $mysqld = can_run($bin)) {
52 0 0       0 $found{server_bin} = $mysqld if $this->verify_provider($mysqld);
53 0 0       0 last if $found{server_bin};
54             }
55             }
56              
57 23 50       4866 return $PROVIDER_CACHE{$class} = {} unless $found{server_bin};
58              
59 0         0 for my $bin ($this->client_bin_list) {
60 0 0       0 if (my $mysql = can_run($bin)) {
61 0         0 $found{client_bin} = $mysql;
62 0         0 last;
63             }
64             }
65              
66 0 0       0 return $PROVIDER_CACHE{$class} = {} unless $found{client_bin};
67              
68 0         0 for my $bin ($this->install_bin_list) {
69 0 0       0 if (my $install = can_run($bin)) {
70 0     0   0 my ($stdout, $stderr) = capture { system($install) };
  0         0  
71 0         0 my $output = $stdout . "\n" . $stderr;
72 0 0       0 unless ($output =~ m/is deprecated/) {
73 0         0 $found{install_bin} = $install;
74             }
75 0 0       0 last if $found{install_bin};
76             }
77             }
78              
79 0         0 return $PROVIDER_CACHE{$class} = \%found;
80             }
81              
82             sub choose {
83 0     0 0 0 my $this = shift;
84              
85 0         0 my $spec = { bootstrap => 1, load_sql => 1 };
86              
87 0         0 require DBIx::QuickDB::Driver::MariaDB;
88 0         0 my ($ok, $why) = DBIx::QuickDB::Driver::MariaDB->viable($spec);
89 0 0       0 return 'DBIx::QuickDB::Driver::MariaDB' if $ok;
90              
91 0         0 require DBIx::QuickDB::Driver::MySQLCom;
92 0         0 ($ok, $why) = DBIx::QuickDB::Driver::MySQLCom->viable($spec);
93 0 0       0 return 'DBIx::QuickDB::Driver::MySQLCom' if $ok;
94              
95 0         0 require DBIx::QuickDB::Driver::Percona;
96 0         0 ($ok, $why) = DBIx::QuickDB::Driver::Percona->viable($spec);
97 0 0       0 return 'DBIx::QuickDB::Driver::Percona' if $ok;
98              
99 0         0 return undef;
100             }
101              
102             sub viable {
103 7     7 1 12 my $this = shift;
104 7         12 my ($spec) = @_;
105              
106 7         2627 require DBIx::QuickDB::Driver::MariaDB;
107 7         2070 require DBIx::QuickDB::Driver::MySQLCom;
108 7         1844 require DBIx::QuickDB::Driver::Percona;
109              
110 7         43 my ($ok1, $why1) = DBIx::QuickDB::Driver::MariaDB->viable($spec);
111 7         32 my ($ok2, $why2) = DBIx::QuickDB::Driver::MySQLCom->viable($spec);
112 7         29 my ($ok3, $why3) = DBIx::QuickDB::Driver::Percona->viable($spec);
113              
114 7 50 33     37 return (1, undef) if $ok1 || $ok2 || $ok3;
      33        
115              
116 7         33 return (0, join("\n" => $why1, $why2, $why3));
117             }
118              
119             sub new {
120 0     0 0 0 my $class = shift;
121              
122 0 0       0 if ($class eq __PACKAGE__) {
123 0 0       0 my $real_class = $class->choose or croak("Neither MariaDB, MySQL (Oracle/Community), or Percona are viable");
124 0         0 return $real_class->new(@_);
125             }
126              
127 0 0       0 my $self = @_ == 1 ? $_[0] : {@_};
128 0         0 bless($self, $class);
129 0         0 $self->init();
130              
131 0         0 return $self;
132             }
133              
134             sub version_string {
135 0     0 1 0 my ($class_or_self, @other) = @_;
136              
137 0         0 my $binary;
138              
139             # Go in reverse order assuming the last param hash provided is most important
140 0         0 for my $arg (reverse @_) {
141 0 0       0 my $type = reftype($arg) or next; # skip if not a ref
142 0 0       0 next unless $type eq 'HASH'; # We have a hashref, possibly blessed
143              
144             # If we find a launcher we are done looping, we want to use this binary.
145 0 0 0     0 if (blessed($arg) && $arg->can('server_bin')) {
146 0 0       0 $binary = $arg->server_bin and last;
147             }
148              
149 0         0 for my $l (qw/server_bin mysqld mariadbd/) {
150 0 0       0 $binary = $arg->{$l} and last;
151             }
152              
153 0 0       0 last if $binary;
154             }
155              
156 0 0       0 unless ($binary) {
157 0 0       0 if ($class_or_self eq __PACKAGE__) {
158 0 0       0 if (my $sel = $class_or_self->choose) {
159 0         0 $binary = $sel->server_bin;
160             }
161             }
162             else {
163 0         0 $binary = $class_or_self->server_bin;
164             }
165             }
166              
167 0 0       0 croak "Could not find a viable server binary" unless $binary;
168              
169             # Call the binary with '-V', capturing and returning the output using backticks.
170 0     0   0 my ($v, $stderr) = capture { system($binary, '-V') };
  0         0  
171              
172 0         0 return $v;
173             }
174              
175             sub dbd_driver {
176 40     40 1 95 my $in = shift;
177              
178 40 0 33     120 return $in->{+DBD_DRIVER} if blessed($in) && $in->{+DBD_DRIVER};
179              
180 40         85 for my $driver ($in->dbd_driver_order) {
181 80         119 my $file = $driver;
182 80         228 $file =~ s{::}{/}g;
183 80         191 $file .= ".pm";
184 80 50       116 eval { require($file); 1 } or next;
  80         5164  
  0         0  
185              
186 0 0       0 return $in->{+DBD_DRIVER} = $driver if blessed($in);
187 0         0 return $driver;
188             }
189              
190 40         200 return undef;
191             }
192              
193             sub list_env_vars {
194 0     0 1 0 my $self = shift;
195             return (
196 0         0 $self->SUPER::list_env_vars(),
197             qw{
198             LIBMYSQL_ENABLE_CLEARTEXT_PLUGIN LIBMYSQL_PLUGINS
199             LIBMYSQL_PLUGIN_DIR MYSQLX_TCP_PORT MYSQLX_UNIX_PORT MYSQL_DEBUG
200             MYSQL_GROUP_SUFFIX MYSQL_HISTFILE MYSQL_HISTIGNORE MYSQL_HOME
201             MYSQL_HOST MYSQL_OPENSSL_UDF_DH_BITS_THRESHOLD
202             MYSQL_OPENSSL_UDF_DSA_BITS_THRESHOLD
203             MYSQL_OPENSSL_UDF_RSA_BITS_THRESHOLD MYSQL_PS1 MYSQL_PWD
204             MYSQL_SERVER_PREPARE MYSQL_TCP_PORT MYSQL_TEST_LOGIN_FILE
205             MYSQL_TEST_TRACE_CRASH MYSQL_TEST_TRACE_DEBUG MYSQL_UNIX_PORT
206             }
207             );
208             }
209              
210             sub _default_paths {
211 40     40   96 my $class = shift;
212              
213             return (
214 40         155 server => $class->server_bin,
215             client => $class->client_bin,
216             );
217             }
218              
219             sub _default_config {
220 0     0     my $self = shift;
221              
222 0           my $dir = $self->dir;
223 0           my $data_dir = $self->data_dir;
224 0           my $temp_dir = $self->temp_dir;
225 0           my $pid_file = $self->pid_file;
226 0           my $socket = $self->socket;
227              
228             return (
229             client => {
230             'socket' => $socket,
231             },
232              
233             mysql_safe => {
234             'socket' => $socket,
235             },
236              
237             mysql => {
238             'socket' => $socket,
239             },
240              
241             mysqld => {
242             'datadir' => $data_dir,
243             'pid-file' => $pid_file,
244             'socket' => $socket,
245             'tmpdir' => $temp_dir,
246              
247             'log_error' => "$dir/error.log",
248             'secure_file_priv' => $dir,
249             'default_storage_engine' => 'InnoDB',
250             'innodb_buffer_pool_size' => '20M',
251             'key_buffer_size' => '20M',
252             'max_connections' => '100',
253             'server-id' => '1',
254             'skip_grant_tables' => '1',
255             'skip_external_locking' => '',
256             'skip_networking' => '1',
257             'skip_name_resolve' => '1',
258             'max_allowed_packet' => '1M',
259             'max_binlog_size' => '20M',
260             'myisam_sort_buffer_size' => '8M',
261             'net_buffer_length' => '8K',
262             'read_buffer_size' => '256K',
263             'read_rnd_buffer_size' => '512K',
264             'sort_buffer_size' => '512K',
265             'table_open_cache' => '64',
266             'thread_cache_size' => '8',
267             'thread_stack' => '192K',
268             'innodb_io_capacity' => '2000',
269             'innodb_max_dirty_pages_pct' => '0',
270             'innodb_max_dirty_pages_pct_lwm' => '0',
271              
272             'character_set_server' => $self->{+CHARACTER_SET_SERVER},
273              
274 0 0         defined($ENV{QDB_MYSQL_SSL_FIPS}) ? ('ssl_fips_mode' => "$ENV{QDB_MYSQL_SSL_FIPS}") : (),
275             },
276             );
277             }
278              
279             sub init {
280 0     0 1   my $self = shift;
281 0           $self->SUPER::init();
282              
283 0           $self->dbd_driver; # Vivify this
284              
285 0   0       $self->{+CHARACTER_SET_SERVER} //= 'UTF8MB4';
286              
287 0           $self->{+DATA_DIR} = $self->{+DIR} . '/data';
288 0           $self->{+TEMP_DIR} = $self->{+DIR} . '/temp';
289 0           $self->{+CFG_FILE} = $self->{+DIR} . '/my.cfg';
290 0           $self->{+PID_FILE} = $self->{+DIR} . '/mysql.pid';
291 0   0       $self->{+SOCKET} ||= $self->{+DIR} . '/mysql.sock';
292              
293 0   0       $self->{+USERNAME} ||= 'root';
294              
295 0           my %defaults = $self->_default_paths;
296 0   0       $self->{$_} ||= $defaults{$_} for keys %defaults;
297              
298 0           my %cfg_defs = $self->_default_config;
299 0 0         my $cfg = { %{$self->{+CONFIG} || {}} };
  0            
300 0           $self->{+CONFIG} = $cfg;
301              
302 0           for my $key (keys %cfg_defs) {
303 0 0         if (defined $cfg->{$key}) {
304 0           my $subdft = $cfg_defs{$key};
305 0           my $subcfg = { %{$cfg->{$key}} };
  0            
306 0           $cfg->{$key} = $subcfg;
307              
308 0           for my $skey (keys %$subdft) {
309 0 0         next if defined $subcfg->{$skey};
310 0           $subcfg->{$skey} = $subdft->{$skey};
311             }
312             }
313             else {
314 0           $cfg->{$key} = $cfg_defs{$key};
315             }
316             }
317             }
318              
319             sub clone_data {
320 0     0 1   my $self = shift;
321              
322             my $config = strip_hash_defaults(
323 0           $self->{+CONFIG},
324             {$self->_default_config},
325             );
326              
327             return (
328             $self->SUPER::clone_data(),
329              
330             CONFIG() => $config,
331 0           DBD_DRIVER() => $self->{+DBD_DRIVER},
332             );
333             }
334              
335             sub write_config {
336 0     0 1   my $self = shift;
337 0           my (%params) = @_;
338              
339 0           my $cfg_file = $self->{+CFG_FILE};
340 0 0         open(my $cfh, '>', $cfg_file) or die "Could not open config file: $!";
341 0           my $conf = $self->{+CONFIG};
342 0           for my $section (sort keys %$conf) {
343 0   0       my $override = $params{$section} // {};
344              
345 0 0         my $sconf = $conf->{$section} or next;
346              
347 0 0         $sconf = { %$sconf, %{$override->{add}} } if $override->{add};
  0            
348              
349 0           print $cfh "[$section]\n";
350 0           for my $key (sort keys %$sconf) {
351 0           my $val = $sconf->{$key};
352 0 0         next unless defined $val;
353              
354 0 0 0       next if $override->{skip} && ($key =~ $override->{skip} || $val =~ $override->{skip});
      0        
355              
356 0 0         if (length($val)) {
357 0           print $cfh "$key = $val\n";
358             }
359             else {
360 0           print $cfh "$key\n";
361             }
362             }
363              
364 0           print $cfh "\n";
365             }
366 0           close($cfh);
367              
368 0           return;
369             }
370              
371             sub bootstrap {
372 0     0     my $self = shift;
373              
374 0           my $data_dir = $self->{+DATA_DIR};
375 0           my $temp_dir = $self->{+TEMP_DIR};
376              
377 0 0         mkdir($data_dir) or die "Could not create data dir: $!";
378 0 0         mkdir($temp_dir) or die "Could not create temp dir: $!";
379              
380 0           my $init_file = "$self->{+DIR}/init.sql";
381 0 0         open(my $init, '>', $init_file) or die "Could not open init file: $!";
382 0           print $init "CREATE DATABASE quickdb;\n";
383 0           close($init);
384              
385 0           return $init_file;
386             }
387              
388             sub load_sql {
389 0     0 1   my $self = shift;
390 0           my ($db_name, $file) = @_;
391              
392 0           my $cfg_file = $self->{+CFG_FILE};
393              
394 0           $self->run_command(
395             [
396             $self->client_bin,
397             "--defaults-file=$cfg_file",
398             '-u' => 'root',
399             $db_name,
400             ],
401             {stdin => $file},
402             );
403             }
404              
405             sub shell_command {
406 0     0 1   my $self = shift;
407 0           my ($db_name) = @_;
408              
409 0           my $cfg_file = $self->{+CFG_FILE};
410 0           return ($self->client_bin, "--defaults-file=$cfg_file", $db_name);
411             }
412              
413             sub start_command {
414 0     0 1   my $self = shift;
415              
416 0           my $cfg_file = $self->{+CFG_FILE};
417 0           return ($self->server_bin, "--defaults-file=$cfg_file", '--skip-grant-tables');
418             }
419              
420             sub connect_string {
421 0     0 1   my $self = shift;
422 0           my ($db_name) = @_;
423 0 0         $db_name = 'quickdb' unless defined $db_name;
424              
425 0           my $socket = $self->{+SOCKET};
426              
427 0 0         if ($self->dbd_driver eq 'DBD::MariaDB') {
428 0           return "dbi:MariaDB:dbname=$db_name;mariadb_socket=$socket";
429             }
430             else {
431 0           return "dbi:mysql:dbname=$db_name;mysql_socket=$socket";
432             }
433             }
434              
435              
436             1;
437              
438             __END__