File Coverage

blib/lib/DBIx/QuickDB/Driver/MySQL.pm
Criterion Covered Total %
statement 46 181 25.4
branch 9 90 10.0
condition 4 58 6.9
subroutine 11 22 50.0
pod 10 10 100.0
total 80 361 22.1


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