File Coverage

blib/lib/DBIx/QuickDB/Driver/MySQL.pm
Criterion Covered Total %
statement 62 208 29.8
branch 6 90 6.6
condition 4 33 12.1
subroutine 17 37 45.9
pod 11 23 47.8
total 100 391 25.5


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