File Coverage

blib/lib/DBIx/QuickDB/Driver/MySQL.pm
Criterion Covered Total %
statement 45 173 26.0
branch 8 82 9.7
condition 4 60 6.6
subroutine 11 22 50.0
pod 10 10 100.0
total 78 347 22.4


line stmt bran cond sub pod time code
1             package DBIx::QuickDB::Driver::MySQL;
2 6     6   463349 use strict;
  6         28  
  6         195  
3 6     6   34 use warnings;
  6         13  
  6         269  
4              
5             our $VERSION = '0.000021';
6              
7 6     6   3677 use IPC::Cmd qw/can_run/;
  6         262942  
  6         419  
8 6     6   3250 use DBIx::QuickDB::Util qw/strip_hash_defaults/;
  6         19  
  6         68  
9 6     6   291 use Scalar::Util qw/reftype/;
  6         14  
  6         395  
10 6     6   39 use Carp qw/confess/;
  6         16  
  6         289  
11              
12 6     6   1039 use parent 'DBIx::QuickDB::Driver';
  6         664  
  6         53  
13              
14 6         37 use DBIx::QuickDB::Util::HashBase qw{
15             -data_dir -temp_dir -socket -pid_file -cfg_file
16              
17             -mysqld -mysql
18              
19             -dbd_driver
20             -mysqld_provider
21             -use_bootstrap
22             -use_installdb
23              
24             -character_set_server
25              
26             -config
27 6     6   467 };
  6         24  
28              
29             my ($MYSQLD, $MYSQL, $DBDMYSQL, $DBDMARIA, $INSTALLDB);
30              
31             BEGIN {
32 6     6   30 local $@;
33              
34 6         42 $MYSQLD = can_run('mysqld');
35 6         2392 $MYSQL = can_run('mysql');
36 6         2001 $INSTALLDB = can_run('mysql_install_db');
37              
38 6         1989 $DBDMYSQL = eval { require DBD::mysql; 'DBD::mysql' };
  6         1062  
  0         0  
39 6         29 $DBDMARIA = eval { require DBD::MariaDB; 'DBD::MariaDB' };
  6         14672  
  0         0  
40             }
41              
42             sub version_string {
43 0     0 1 0 my $binary;
44              
45             # Go in reverse order assuming the last param hash provided is most important
46 0         0 for my $arg (reverse @_) {
47 0 0       0 my $type = reftype($arg) or next; # skip if not a ref
48 0 0       0 next unless $type eq 'HASH'; # We have a hashref, possibly blessed
49              
50             # If we find a launcher we are done looping, we want to use this binary.
51 0 0       0 $binary = $arg->{+MYSQLD} and last;
52             }
53              
54             # If no args provided one to use we fallback to the default from $PATH
55 0   0     0 $binary ||= $MYSQLD;
56              
57             # Call the binary with '-V', capturing and returning the output using backticks.
58 0         0 return `$binary -V`;
59             }
60              
61             sub list_env_vars {
62 0     0 1 0 my $self = shift;
63             return (
64 0         0 $self->SUPER::list_env_vars(),
65             qw{
66             LIBMYSQL_ENABLE_CLEARTEXT_PLUGIN LIBMYSQL_PLUGINS
67             LIBMYSQL_PLUGIN_DIR MYSQLX_TCP_PORT MYSQLX_UNIX_PORT MYSQL_DEBUG
68             MYSQL_GROUP_SUFFIX MYSQL_HISTFILE MYSQL_HISTIGNORE MYSQL_HOME
69             MYSQL_HOST MYSQL_OPENSSL_UDF_DH_BITS_THRESHOLD
70             MYSQL_OPENSSL_UDF_DSA_BITS_THRESHOLD
71             MYSQL_OPENSSL_UDF_RSA_BITS_THRESHOLD MYSQL_PS1 MYSQL_PWD
72             MYSQL_SERVER_PREPARE MYSQL_TCP_PORT MYSQL_TEST_LOGIN_FILE
73             MYSQL_TEST_TRACE_CRASH MYSQL_TEST_TRACE_DEBUG MYSQL_UNIX_PORT
74             }
75             );
76             }
77              
78             sub _default_paths {
79             return (
80 6     6   43 mysqld => $MYSQLD,
81             mysql => $MYSQL,
82             );
83             }
84              
85             sub _default_config {
86 0     0   0 my $self = shift;
87              
88 0         0 my $dir = $self->dir;
89 0         0 my $data_dir = $self->data_dir;
90 0         0 my $temp_dir = $self->temp_dir;
91 0         0 my $pid_file = $self->pid_file;
92 0         0 my $socket = $self->socket;
93              
94 0         0 my $provider = $self->{+MYSQLD_PROVIDER};
95              
96             return (
97             client => {
98             'socket' => $socket,
99             },
100              
101             mysql_safe => {
102             'socket' => $socket,
103             },
104              
105             mysqld => {
106             'datadir' => $data_dir,
107             'pid-file' => $pid_file,
108             'socket' => $socket,
109             'tmpdir' => $temp_dir,
110              
111             'secure_file_priv' => $dir,
112             'default_storage_engine' => 'InnoDB',
113             'innodb_buffer_pool_size' => '20M',
114             'key_buffer_size' => '20M',
115             'max_connections' => '100',
116             'server-id' => '1',
117             'skip_grant_tables' => '1',
118             'skip_external_locking' => '',
119             'skip_networking' => '1',
120             'skip_name_resolve' => '1',
121             'max_allowed_packet' => '1M',
122             'max_binlog_size' => '20M',
123             'myisam_sort_buffer_size' => '8M',
124             'net_buffer_length' => '8K',
125             'read_buffer_size' => '256K',
126             'read_rnd_buffer_size' => '512K',
127             'sort_buffer_size' => '512K',
128             'table_open_cache' => '64',
129             'thread_cache_size' => '8',
130             'thread_stack' => '192K',
131             'innodb_io_capacity' => '2000',
132             'innodb_max_dirty_pages_pct' => '0',
133             'innodb_max_dirty_pages_pct_lwm' => '0',
134              
135             $provider eq 'percona'
136             ? (
137             'character_set_server' => $self->{+CHARACTER_SET_SERVER},
138             )
139             : (
140 0 0       0 'character_set_server' => $self->{+CHARACTER_SET_SERVER},
141             'query_cache_limit' => '1M',
142             'query_cache_size' => '20M',
143             ),
144             },
145              
146             mysql => {
147             'socket' => $socket,
148             'no-auto-rehash' => '',
149             },
150             );
151             }
152              
153             sub viable {
154 6     6 1 17 my $this = shift;
155 6         14 my ($spec) = @_;
156              
157 6 50       34 my %check = (ref($this) ? %$this : (), $this->_default_paths, %$spec);
158              
159 6         16 my @bad;
160              
161 6 50 33     49 push @bad => "Could not load either 'DBD::mysql' or 'DBD::MariaDB', needed for everything"
162             unless $DBDMYSQL || $DBDMARIA;
163              
164 6 50       27 if ($spec->{bootstrap}) {
    0          
165 6 50 33     44 push @bad => "'mysqld' command is missing, needed for bootstrap" unless $check{mysqld} && -x $check{mysqld};
166             }
167             elsif ($spec->{autostart}) {
168 0 0 0     0 push @bad => "'mysqld' command is missing, needed for autostart" unless $check{mysqld} && -x $check{mysqld};
169             }
170              
171 6 50       21 if ($spec->{load_sql}) {
172 6 50 33     28 push @bad => "'mysql' command is missing, needed for load_sql" unless $check{mysql} && -x $check{mysql};
173             }
174              
175 6 50 33     31 if ($check{+MYSQLD} || $MYSQLD) {
176 0         0 my $version = $this->version_string;
177 0 0 0     0 if ($version && $version =~ m/(\d+)\.(\d+)\.(\d+)/) {
178 0         0 my ($a, $b, $c) = ($1, $2, $3);
179 0 0 0     0 push @bad => "'mysqld' is too old ($a.$b.$c), need at least 5.6.0"
      0        
180             if $a < 5 || ($a == 5 && $b < 6);
181             }
182             }
183              
184 6 50       19 return (1, undef) unless @bad;
185 6         42 return (0, join "\n" => @bad);
186             }
187              
188             sub init {
189 0     0 1   my $self = shift;
190 0           $self->SUPER::init();
191              
192             # Percona is the more restrictive, so fallback to mariadb behavior for
193             # now. Add patches for more variants if needed.
194 0 0         unless ($self->{+MYSQLD_PROVIDER}) {
195 0 0         if ($self->version_string =~ m/(mariadb|percona)/i) {
196 0           $self->{+MYSQLD_PROVIDER} = lc($1);
197              
198 0 0         if ($self->{+MYSQLD_PROVIDER} eq 'percona') {
199 0   0       my $binary = $self->{+MYSQLD} || $MYSQLD;
200 0           my $help = `$binary --help --verbose 2>&1`;
201              
202 0 0         if ($help =~ m/--bootstrap/) {
203 0           $self->{+USE_BOOTSTRAP} = 1;
204              
205 0 0         $self->{+USE_INSTALLDB} = $INSTALLDB ? 1 : 0;
206             }
207             }
208             }
209             else {
210 0   0       my $binary = $self->{+MYSQLD} || $MYSQLD;
211 0           my $help = `$binary --help --verbose 2>&1`;
212              
213 0 0         if ($help =~ m/(mariadb|percona)/i) {
    0          
    0          
214 0           $self->{+MYSQLD_PROVIDER} = lc($1);
215             }
216             elsif ($help =~ m/--bootstrap/) {
217 0           $self->{+MYSQLD_PROVIDER} = 'mariadb';
218             }
219             elsif ($help =~ m/--initialize/) {
220 0           $self->{+MYSQLD_PROVIDER} = 'percona';
221             }
222             }
223             }
224              
225             confess "Could not determine mysqld provider (" . ($self->{+MYSQLD} || $MYSQLD) . ") please specify mysqld_prover => mariadb|percona"
226 0 0 0       unless $self->{+MYSQLD_PROVIDER};
227              
228 0   0       $self->{+DBD_DRIVER} //= $DBDMARIA || $DBDMYSQL;
      0        
229              
230 0   0       $self->{+CHARACTER_SET_SERVER} //= 'UTF8MB4';
231              
232 0           $self->{+DATA_DIR} = $self->{+DIR} . '/data';
233 0           $self->{+TEMP_DIR} = $self->{+DIR} . '/temp';
234 0           $self->{+PID_FILE} = $self->{+DIR} . '/mysql.pid';
235 0           $self->{+CFG_FILE} = $self->{+DIR} . '/my.cfg';
236              
237 0   0       $self->{+SOCKET} ||= $self->{+DIR} . '/mysql.sock';
238              
239 0   0       $self->{+USERNAME} ||= 'root';
240              
241 0           my %defaults = $self->_default_paths;
242 0   0       $self->{$_} ||= $defaults{$_} for keys %defaults;
243              
244 0           my %cfg_defs = $self->_default_config;
245 0   0       my $cfg = $self->{+CONFIG} ||= {};
246              
247 0           for my $key (keys %cfg_defs) {
248 0 0         if (defined $cfg->{$key}) {
249 0           my $subdft = $cfg_defs{$key};
250 0           my $subcfg = $cfg->{$key};
251              
252 0           for my $skey (%$subdft) {
253 0 0         next if defined $subcfg->{$skey};
254 0           $subcfg->{$skey} = $subdft->{$skey};
255             }
256             }
257             else {
258 0           $cfg->{$key} = $cfg_defs{$key};
259             }
260             }
261             }
262              
263             sub clone_data {
264 0     0 1   my $self = shift;
265              
266             my $config = strip_hash_defaults(
267 0           $self->{+CONFIG},
268             { $self->_default_config },
269             );
270              
271             return (
272             $self->SUPER::clone_data(),
273              
274             CONFIG() => $config,
275             MYSQLD() => $self->{+MYSQLD},
276             MYSQL() => $self->{+MYSQL},
277             DBD_DRIVER() => $self->{+DBD_DRIVER},
278 0           MYSQLD_PROVIDER() => $self->{+MYSQLD_PROVIDER},
279             );
280             }
281              
282             sub write_config {
283 0     0 1   my $self = shift;
284 0           my (%params) = @_;
285              
286 0           my $cfg_file = $self->{+CFG_FILE};
287 0 0         open(my $cfh, '>', $cfg_file) or die "Could not open config file: $!";
288 0           my $conf = $self->{+CONFIG};
289 0           for my $section (sort keys %$conf) {
290 0 0         my $sconf = $conf->{$section} or next;
291              
292 0 0         $sconf = { %$sconf, %{$params{add}} } if $params{add};
  0            
293              
294 0           print $cfh "[$section]\n";
295 0           for my $key (sort keys %$sconf) {
296 0           my $val = $sconf->{$key};
297 0 0         next unless defined $val;
298              
299 0 0 0       next if $params{skip} && ($key =~ $params{skip} || $val =~ $params{skip});
      0        
300              
301 0 0         if (length($val)) {
302 0           print $cfh "$key = $val\n";
303             }
304             else {
305 0           print $cfh "$key\n";
306             }
307             }
308              
309 0           print $cfh "\n";
310             }
311 0           close($cfh);
312              
313 0           return;
314             }
315              
316             sub bootstrap {
317 0     0     my $self = shift;
318              
319 0           my $data_dir = $self->{+DATA_DIR};
320 0           my $temp_dir = $self->{+TEMP_DIR};
321              
322 0 0         mkdir($data_dir) or die "Could not create data dir: $!";
323 0 0         mkdir($temp_dir) or die "Could not create temp dir: $!";
324              
325              
326 0           my $init_file = "$self->{+DIR}/init.sql";
327 0 0         open(my $init, '>', $init_file) or die "Could not open init file: $!";
328 0           print $init "CREATE DATABASE quickdb;\n";
329 0           close($init);
330              
331 0           my $provider = $self->{+MYSQLD_PROVIDER};
332              
333 0 0         if ($provider eq 'percona') {
334              
335 0 0         if ($self->{+USE_BOOTSTRAP}) {
336 0 0         if($self->{+USE_INSTALLDB}) {
337 0           local $ENV{PERL5LIB} = "";
338 0           $self->run_command([$INSTALLDB, '--datadir=' . $data_dir]);
339             }
340 0           $self->write_config();
341 0           $self->run_command([$self->start_command, '--bootstrap'], {stdin => $init_file});
342             }
343             else {
344 0           $self->write_config();
345 0           $self->run_command([$self->start_command, '--initialize']);
346 0           $self->start;
347 0           $self->load_sql("", $init_file);
348             }
349             }
350             else {
351             # Bootstrap is much faster without InnoDB, we will turn InnoDB back on later, and things will use it.
352 0           $self->write_config(skip => qr/innodb/i, add => {'default-storage-engine' => 'MyISAM'});
353 0           $self->run_command([$self->start_command, '--bootstrap'], {stdin => $init_file});
354              
355             # Turn InnoDB back on
356 0           $self->write_config();
357             }
358              
359 0           return;
360             }
361              
362             sub load_sql {
363 0     0 1   my $self = shift;
364 0           my ($db_name, $file) = @_;
365              
366 0           my $cfg_file = $self->{+CFG_FILE};
367              
368             $self->run_command(
369             [
370 0           $self->{+MYSQL},
371             "--defaults-file=$cfg_file",
372             '-u' => 'root',
373             $db_name,
374             ],
375             {stdin => $file},
376             );
377             }
378              
379             sub shell_command {
380 0     0 1   my $self = shift;
381 0           my ($db_name) = @_;
382              
383 0           my $cfg_file = $self->{+CFG_FILE};
384 0           return ($self->{+MYSQL}, "--defaults-file=$cfg_file", $db_name);
385             }
386              
387             sub start_command {
388 0     0 1   my $self = shift;
389              
390 0           my $cfg_file = $self->{+CFG_FILE};
391 0           return ($self->{+MYSQLD}, "--defaults-file=$cfg_file", '--skip-grant-tables');
392             }
393              
394             sub connect_string {
395 0     0 1   my $self = shift;
396 0           my ($db_name) = @_;
397 0 0         $db_name = 'quickdb' unless defined $db_name;
398              
399 0           my $socket = $self->{+SOCKET};
400              
401 0 0         if ($self->{+DBD_DRIVER} eq 'DBD::MariaDB') {
402 0           return "dbi:MariaDB:dbname=$db_name;mariadb_socket=$socket";
403             }
404             else {
405 0           return "dbi:mysql:dbname=$db_name;mysql_socket=$socket";
406             }
407             }
408              
409             1;
410              
411             __END__