1 package MogileFS
::Store
::MySQL
;
6 use MogileFS
::Util
qw(throw);
7 use base
'MogileFS::Store';
9 # --------------------------------------------------------------------------
10 # Package methods we override
11 # --------------------------------------------------------------------------
14 my ($class, $dbname, $host, $port) = @_;
15 return "DBI:mysql:$dbname;host=$host" . ($port ?
";port=$port" : "");
19 my ($class, $dbname, $host, $port) = @_;
20 return $class->dsn_of_dbhost('mysql', $host, $port);
23 # --------------------------------------------------------------------------
24 # Store-related things we override
25 # --------------------------------------------------------------------------
30 $self->{lock_depth
} = 0;
31 $self->{slave_next_check
} = 0;
34 sub post_dbi_connect
{
36 $self->SUPER::post_dbi_connect
;
37 $self->{lock_depth
} = 0;
40 sub was_deadlock_error
{
43 return 0 unless $dbh->err;
44 # 1205 is "lock wait timeout", but we should bomb out if we've
45 # alerady hung for that long.
46 return 1 if ($dbh->err == 1213);
49 sub was_duplicate_error
{
52 return 0 unless $dbh->err;
53 return 1 if $dbh->err == 1062 || $dbh->errstr =~ /duplicate/i;
57 my ($self, $table) = @_;
59 my $sth = $self->dbh->prepare("DESCRIBE $table");
61 my $rec = $sth->fetchrow_hashref;
67 sub can_insertignore
{ 1 }
68 sub can_insert_multi
{ 1 }
69 sub unix_timestamp
{ "UNIX_TIMESTAMP()" }
71 sub filter_create_sql
{
72 my ($self, $sql) = @_;
73 return $sql unless $self->fid_type eq "BIGINT";
74 $sql =~ s!\bfid\s+INT\b!fid BIGINT!i;
78 sub can_do_slaves
{ 1 }
83 return 0 unless $self->{slave
};
85 my $next_check = \
$self->{slave_next_check
};
87 if ($$next_check > time()) {
91 my $master_status = eval { $self->dbh->selectrow_hashref("SHOW MASTER STATUS") };
92 warn "Error thrown: '$@' while trying to get master status." if $@
;
94 my $slave_status = eval { $self->{slave
}->dbh->selectrow_hashref("SHOW SLAVE STATUS") };
95 warn "Error thrown: '$@' while trying to get slave status." if $@
;
97 # compare contrast, return 0 if not okay.
98 # Master: File Position
101 # call time() again here because SQL blocks.
102 $$next_check = time() + 5;
107 # attempt to grab a lock of lockname, and timeout after timeout seconds.
108 # returns 1 on success and 0 on timeout
110 my ($self, $lockname, $timeout) = @_;
111 die "Lock recursion detected (grabbing $lockname, had $self->{last_lock}). Bailing out." if $self->{lock_depth
};
113 my $lock = $self->dbh->selectrow_array("SELECT GET_LOCK(?, ?)", undef, $lockname, $timeout);
115 $self->{lock_depth
} = 1;
116 $self->{last_lock
} = $lockname;
121 # attempt to release a lock of lockname.
122 # returns 1 on success and 0 if no lock we have has that name.
124 my ($self, $lockname) = @_;
125 my $rv = $self->dbh->selectrow_array("SELECT RELEASE_LOCK(?)", undef, $lockname);
126 $self->{lock_depth
} = 0;
130 # clears everything from the fsck_log table
131 # return 1 on success. die otherwise.
132 # Under MySQL 4.1+ this is actually fast.
135 $self->dbh->do("TRUNCATE TABLE fsck_log");
139 # --------------------------------------------------------------------------
140 # Functions specific to Store::MySQL subclass. Not in parent.
141 # --------------------------------------------------------------------------
145 return $self->{_fid_type
} if $self->{_fid_type
};
147 # let people force bigint mode with environment.
148 if ($ENV{MOG_FIDSIZE
} && $ENV{MOG_FIDSIZE
} eq "big") {
149 return $self->{_fid_type
} = "BIGINT";
152 # else, check a maybe-existing table and see if we're in bigint
154 my $dbh = $self->dbh;
155 my @create = eval { $dbh->selectrow_array("SHOW CREATE TABLE file") };
156 if (@create && $create[0] eq 'file') {
157 if ($create[1] =~ /\bfid\b.+\bbigint\b/i) {
158 return $self->{_fid_type
} = "BIGINT";
160 return $self->{_fid_type
} = "INT";
164 # Used to default to 32bit ints, but this always bites people
165 # a few years down the road. So default to 64bit.
166 return $self->{_fid_type
} = "BIGINT";
170 my ($self, $table, $col) = @_;
171 my $sth = $self->dbh->prepare("DESCRIBE $table");
173 while (my $rec = $sth->fetchrow_hashref) {
174 if ($rec->{Field
} eq $col) {
182 # --------------------------------------------------------------------------
183 # Test suite things we override
184 # --------------------------------------------------------------------------
189 my $dbname = $args{dbname
} || "tmp_mogiletest";
190 my $host = $args{dbhost
} || 'localhost';
191 my $port = $args{dbport
} || 3306;
192 my $user = $args{dbuser
} || 'root';
193 my $pass = $args{dbpass
} || '';
194 my $rootuser = $args{dbrootuser
} || $args{dbuser
} || 'root';
195 my $rootpass = $args{dbrootpass
} || $args{dbpass
} || '';
197 MogileFS
::Store
->new_from_dsn_user_pass("DBI:mysql:database=$dbname;host=$host;port=$port",
198 $rootuser, $rootpass);
201 _create_mysql_db
($dbh, $dbname);
203 # allow MyISAM in the test suite.
204 $ENV{USE_UNSAFE_MYSQL
} = 1 unless defined $ENV{USE_UNSAFE_MYSQL
};
206 my @args = ("$FindBin::Bin/../mogdbsetup", "--yes",
207 "--dbname=$dbname", "--type=MySQL",
208 "--dbhost=$host", "--dbport=$port",
209 "--dbrootuser=$rootuser",
211 push @args, "--dbpass=$pass" unless $pass eq '';
212 push @args, "--dbrootpass=$rootpass" unless $rootpass eq '';
214 and die "Failed to run mogdbsetup (".join(' ',map { "'".$_."'" } @args).").";
216 if($user ne $rootuser) {
217 $sto = MogileFS
::Store
->new_from_dsn_user_pass(
218 "DBI:mysql:database=$dbname;host=$host;port=$port",
223 $dbh->do("use $dbname");
227 sub _create_mysql_db
{
230 _drop_mysql_db
($dbh, $dbname);
231 $dbh->do("CREATE DATABASE $dbname");
237 $dbh->do("DROP DATABASE IF EXISTS $dbname");
240 # --------------------------------------------------------------------------
241 # Database creation time things we override
242 # --------------------------------------------------------------------------
248 my $dbh = $self->dbh;
250 "InnoDB backend is unavailable for use, force creation of tables " .
251 "by setting USE_UNSAFE_MYSQL=1 in your environment and run this " .
254 unless ($ENV{USE_UNSAFE_MYSQL
}) {
255 my $engines = eval { $dbh->selectall_hashref("SHOW ENGINES", "Engine"); };
256 if ($@
&& $dbh->err == 1064) {
257 # syntax error? for MySQL 4.0.x.
258 # who cares. we'll catch it below on the double-check.
261 unless ($engines->{InnoDB
} and
262 $engines->{InnoDB
}->{Support
} =~ m/^(YES|DEFAULT)$/i);
266 my $existed = $self->table_exists($table);
268 $self->SUPER::create_table
(@_);
269 return if $ENV{USE_UNSAFE_MYSQL
};
271 # don't alter an existing table up to InnoDB from MyISAM...
272 # could be costly. but on new tables, no problem...
274 $dbh->do("ALTER TABLE $table ENGINE=InnoDB");
275 warn "DBI reported an error of: '" . $dbh->errstr . "' when trying to " .
276 "alter table type of $table to InnoDB\n" if $dbh->err;
279 # but in any case, let's see if it's already InnoDB or not.
280 my $table_status = $dbh->selectrow_hashref("SHOW TABLE STATUS LIKE '$table'");
282 # if not, either die or warn.
283 unless (($table_status->{Engine
} || $table_status->{Type
} || "") eq "InnoDB") {
285 warn "WARNING: MySQL table that isn't InnoDB: $table\n";
287 die "MySQL didn't change table type to InnoDB as requested.\n\n$errmsg"
293 # --------------------------------------------------------------------------
294 # Data-access things we override
295 # --------------------------------------------------------------------------
297 # update the device count for a given fidid
298 sub update_devcount_atomic
{
299 my ($self, $fidid) = @_;
300 my $lockname = "mgfs:fid:$fidid";
302 my $lock = eval { $self->get_lock($lockname, 10) };
304 # Check to make sure the lock didn't timeout, then we want to bail.
305 return 0 if defined $lock && $lock == 0;
307 # Checking $@ is pointless for the time because we just want to plow ahead
308 # even if the get_lock trapped a recursion and threw a fatal error.
310 $self->update_devcount($fidid);
312 # Don't release the lock if we never got it.
313 $self->release_lock($lockname) if $lock;
317 sub should_begin_replicating_fidid
{
318 my ($self, $fidid) = @_;
319 my $lockname = "mgfs:fid:$fidid:replicate";
320 return 1 if $self->get_lock($lockname, 1);
324 sub note_done_replicating
{
325 my ($self, $fidid) = @_;
326 my $lockname = "mgfs:fid:$fidid:replicate";
327 $self->release_lock($lockname);
330 sub upgrade_add_host_getport
{
332 # see if they have the get port, else update it
333 unless ($self->column_type("host", "http_get_port")) {
334 $self->dowell("ALTER TABLE host ADD COLUMN http_get_port MEDIUMINT UNSIGNED AFTER http_port");
338 sub upgrade_add_host_altip
{
340 unless ($self->column_type("host", "altip")) {
341 $self->dowell("ALTER TABLE host ADD COLUMN altip VARCHAR(15) AFTER hostip");
342 $self->dowell("ALTER TABLE host ADD COLUMN altmask VARCHAR(18) AFTER altip");
343 $self->dowell("ALTER TABLE host ADD UNIQUE altip (altip)");
347 sub upgrade_add_device_asof
{
349 unless ($self->column_type("device", "mb_asof")) {
350 $self->dowell("ALTER TABLE device ADD COLUMN mb_asof INT(10) UNSIGNED AFTER mb_used");
354 sub upgrade_add_device_weight
{
356 unless ($self->column_type("device", "weight")) {
357 $self->dowell("ALTER TABLE device ADD COLUMN weight MEDIUMINT DEFAULT 100 AFTER status");
362 sub upgrade_add_device_readonly
{
364 unless ($self->column_type("device", "status") =~ /readonly/) {
365 $self->dowell("ALTER TABLE device MODIFY COLUMN status ENUM('alive', 'dead', 'down', 'readonly')");
369 sub upgrade_add_device_drain
{
371 unless ($self->column_type("device", "status") =~ /drain/) {
372 $self->dowell("ALTER TABLE device MODIFY COLUMN status ENUM('alive', 'dead', 'down', 'readonly', 'drain')");
376 sub upgrade_modify_server_settings_value
{
378 unless ($self->column_type("server_settings", "value") =~ /text/i) {
379 $self->dowell("ALTER TABLE server_settings MODIFY COLUMN value TEXT");
383 sub upgrade_add_file_to_queue_arg
{
385 unless ($self->column_type("file_to_queue", "arg")) {
386 $self->dowell("ALTER TABLE file_to_queue ADD COLUMN arg TEXT");
390 sub upgrade_modify_device_size
{
392 for my $col ('mb_total', 'mb_used') {
393 if ($self->column_type("device", $col) =~ m/mediumint/i) {
394 $self->dowell("ALTER TABLE device MODIFY COLUMN $col INT UNSIGNED");
399 sub pre_daemonize_checks
{
401 # Jay Buffington, from the mailing lists, writes:
403 # > > Is your DBI version at least 1.43? The Makefile.PL of DBD::mysql shows
404 # > > that code for last_insert_it is compiled in only if DBD::mysql is built
405 # > > with DBI 1.43 or newer.
407 #> jay@webdev:~$ perl -MDBI -le 'print $DBI::VERSION'
410 #> BUT I just re-installed 2.9006 while researching this and my test
411 #> script started working. I just reran the mogile server test suite and
416 #> The original DBD::mysql 2.9006 was installed from a RPM. I bet that
417 #> it was built against a DBI older than 1.43, so it didn't support
421 # since we don't know what version of DBI their DBD::mysql was built against,
422 # let's just test that last_insert_id works.
425 $self->register_tempfile(dmid
=> 99,
426 key
=> "_server_startup_test");
429 die "MySQL self-tests failed. Your DBD::mysql might've been built against an old DBI version.\n";
440 MogileFS::Store::MySQL - MySQL data storage for MogileFS