line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
#!/usr/bin/env perl |
2
|
2
|
|
|
2
|
|
1337
|
use 5.010001; |
|
2
|
|
|
|
|
8
|
|
3
|
2
|
|
|
2
|
|
12
|
use warnings; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
55
|
|
4
|
2
|
|
|
2
|
|
10
|
use strict; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
44
|
|
5
|
2
|
|
|
2
|
|
8
|
use utf8; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
11
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $VERSION = 'v2.3.8'; |
8
|
|
|
|
|
|
|
|
9
|
2
|
|
|
2
|
|
87
|
use FindBin; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
111
|
|
10
|
2
|
|
|
2
|
|
12
|
use lib "$FindBin::Bin/../lib/perl5"; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
14
|
|
11
|
2
|
|
|
2
|
|
298
|
use Narada; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
60
|
|
12
|
2
|
|
|
2
|
|
873
|
use Narada::Lock qw( exclusive_lock unlock_new unlock );; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
10
|
|
13
|
2
|
|
|
2
|
|
4428
|
use Narada::Config qw( get_config get_config_line get_db_config ); |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
10
|
|
14
|
2
|
|
|
2
|
|
250
|
use DBI; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
68
|
|
15
|
2
|
|
|
2
|
|
937
|
use Time::Local; |
|
2
|
|
|
|
|
2833
|
|
|
2
|
|
|
|
|
110
|
|
16
|
2
|
|
|
2
|
|
28
|
use List::Util qw( max ); |
|
2
|
|
|
|
|
16
|
|
|
2
|
|
|
|
|
111
|
|
17
|
2
|
|
|
2
|
|
11
|
use File::Temp qw( tempfile ); |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
84
|
|
18
|
2
|
|
|
2
|
|
12
|
use Fcntl qw(F_SETFD); |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
89
|
|
19
|
|
|
|
|
|
|
|
20
|
2
|
|
|
2
|
|
11
|
use constant IS_NARADA => Narada::detect() eq 'narada'; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
8
|
|
21
|
2
|
|
|
2
|
|
13
|
use constant CONFIG_DIR => IS_NARADA ? 'mysql' : 'db'; |
|
2
|
|
|
|
|
26
|
|
|
2
|
|
|
|
|
124
|
|
22
|
2
|
|
|
2
|
|
13
|
use constant SQL_DIR => IS_NARADA ? 'var/mysql' : 'var/sql'; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
114
|
|
23
|
2
|
|
|
2
|
|
13
|
use constant SCHEME => SQL_DIR.'/db.scheme.sql'; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
81
|
|
24
|
2
|
|
|
2
|
|
9
|
use constant REQUIRED_TABLE => 0; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
92
|
|
25
|
2
|
|
|
2
|
|
12
|
use constant OPTIONAL_TABLE => 1; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
78
|
|
26
|
2
|
|
|
2
|
|
9
|
use constant TIMELOCAL_MONTH=> 4; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
93
|
|
27
|
2
|
|
|
2
|
|
11
|
use constant STAT_MTIME => 9; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
81
|
|
28
|
2
|
|
|
2
|
|
11
|
use constant DESC_FIELD => 0; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
96
|
|
29
|
2
|
|
|
2
|
|
12
|
use constant DESC_TYPE => 1; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
91
|
|
30
|
2
|
|
|
2
|
|
12
|
use constant DESC_NULL => 2; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
83
|
|
31
|
2
|
|
|
2
|
|
10
|
use constant DESC_KEY => 3; |
|
2
|
|
|
|
|
2
|
|
|
2
|
|
|
|
|
98
|
|
32
|
2
|
|
|
2
|
|
11
|
use constant DESC_DEFAULT => 4; |
|
2
|
|
|
|
|
6
|
|
|
2
|
|
|
|
|
80
|
|
33
|
2
|
|
|
2
|
|
10
|
use constant DESC_EXTRA => 5; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
4077
|
|
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
# GLOBALS: $::dbh, $::mycnf, $::MYSQLDUMP |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
main(@ARGV) if !caller; |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
|
41
|
0
|
|
|
0
|
|
|
sub err { die "narada-mysqldump: @_\n" } |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
sub main { |
44
|
0
|
0
|
|
0
|
|
|
die "Usage: narada-mysqldump\n" if @_; |
45
|
|
|
|
|
|
|
|
46
|
0
|
0
|
|
|
|
|
init_globals() or return; |
47
|
|
|
|
|
|
|
|
48
|
0
|
|
|
|
|
|
exclusive_lock(); |
49
|
0
|
|
|
|
|
|
unlock_new(); |
50
|
|
|
|
|
|
|
|
51
|
0
|
|
|
|
|
|
my ($full, $incremental, $ignore) = list_tables(); |
52
|
0
|
|
|
|
|
|
my $unchanged = detect_unchanged($full); |
53
|
|
|
|
|
|
|
|
54
|
0
|
|
|
|
|
|
del_dumps_except($incremental, $unchanged); |
55
|
0
|
|
|
|
|
|
dump_scheme_except($ignore); |
56
|
0
|
|
|
|
|
|
dump_full($full, $unchanged); |
57
|
0
|
|
|
|
|
|
dump_incremental($incremental); |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
# To correctly detect_unchanged() we should guarantee next table's |
60
|
|
|
|
|
|
|
# Update_time after executing narada-mysqldump will be at least 1 |
61
|
|
|
|
|
|
|
# second later than previous (which kept in dump file's mtime). |
62
|
0
|
|
|
|
|
|
sleep 1; |
63
|
|
|
|
|
|
|
|
64
|
0
|
|
|
|
|
|
unlock(); |
65
|
|
|
|
|
|
|
|
66
|
0
|
|
|
|
|
|
return; |
67
|
|
|
|
|
|
|
} |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
sub init_globals { |
70
|
0
|
0
|
|
0
|
|
|
my $db = get_db_config() or return; |
71
|
|
|
|
|
|
|
$::dbh = DBI->connect($db->{dsn}, $db->{login}, $db->{pass}, |
72
|
0
|
0
|
|
|
|
|
{RaiseError=>1}) or err DBI->errstr; |
73
|
0
|
|
|
|
|
|
$::mycnf = tempfile(DIR=>'tmp'); |
74
|
0
|
|
|
|
|
|
print {$::mycnf} "[client]\n"; |
|
0
|
|
|
|
|
|
|
75
|
0
|
|
|
|
|
|
print {$::mycnf} "user = $db->{login}\n"; |
|
0
|
|
|
|
|
|
|
76
|
0
|
0
|
|
|
|
|
print {$::mycnf} "password = $db->{pass}\n" if length $db->{pass}; ## no critic (ProhibitPostfixControls) |
|
0
|
|
|
|
|
|
|
77
|
0
|
0
|
|
|
|
|
print {$::mycnf} "host = $db->{host}\n" if length $db->{host}; ## no critic (ProhibitPostfixControls) |
|
0
|
|
|
|
|
|
|
78
|
0
|
0
|
|
|
|
|
print {$::mycnf} "port = $db->{port}\n" if length $db->{port}; ## no critic (ProhibitPostfixControls) |
|
0
|
|
|
|
|
|
|
79
|
0
|
|
|
|
|
|
$::mycnf->flush; |
80
|
0
|
0
|
|
|
|
|
sysseek $::mycnf, 0, 0 or err "sysseek: $!"; |
81
|
0
|
0
|
|
|
|
|
fcntl $::mycnf, F_SETFD, 0 or err "fcntl: $!"; |
82
|
0
|
|
|
|
|
|
my $fd = fileno $::mycnf; |
83
|
0
|
|
|
|
|
|
$::MYSQLDUMP = "mysqldump --defaults-file=/proc/self/fd/\Q$fd\E \Q$db->{db}\E"; |
84
|
0
|
|
|
|
|
|
return 1; |
85
|
|
|
|
|
|
|
} |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
sub list_tables { |
88
|
0
|
|
|
0
|
|
|
my $incremental = load_db_conf('dump/incremental', REQUIRED_TABLE); |
89
|
0
|
|
|
|
|
|
my $empty = load_db_conf('dump/empty', REQUIRED_TABLE); |
90
|
0
|
|
|
|
|
|
my $ignore = load_db_conf('dump/ignore', OPTIONAL_TABLE); |
91
|
0
|
|
|
|
|
|
my %other = map {$_=>1} @{$incremental}, @{$empty}, @{$ignore}; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
92
|
0
|
|
|
|
|
|
my @full; |
93
|
0
|
|
|
|
|
|
for my $table (@{ $::dbh->selectcol_arrayref('SHOW TABLES') }) { |
|
0
|
|
|
|
|
|
|
94
|
0
|
0
|
|
|
|
|
next if $other{$table}; |
95
|
0
|
|
|
|
|
|
push @full, $table; |
96
|
|
|
|
|
|
|
} |
97
|
0
|
|
|
|
|
|
return ([sort @full], $incremental, $ignore); |
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
sub load_db_conf { |
101
|
0
|
|
|
0
|
|
|
my ($conf, $required) = @_; |
102
|
0
|
|
|
|
|
|
my @conf = eval { split /\s*\n/xms, get_config(CONFIG_DIR."/$conf") }; |
|
0
|
|
|
|
|
|
|
103
|
0
|
|
|
|
|
|
my @tables; |
104
|
0
|
|
|
|
|
|
for my $table (@conf) { |
105
|
0
|
|
|
|
|
|
local ($::dbh->{RaiseError}, $::dbh->{PrintError}); |
106
|
0
|
|
|
|
|
|
my $desc = $::dbh->selectall_arrayref('DESC '.$table); |
107
|
0
|
0
|
|
|
|
|
if ($desc) { |
|
|
0
|
|
|
|
|
|
108
|
0
|
|
|
|
|
|
push @tables, $table; |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
elsif ($required == REQUIRED_TABLE) { |
111
|
0
|
|
|
|
|
|
err "Table $table listed in ".CONFIG_DIR."/$conf does not exists\n"; |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
} |
114
|
0
|
|
|
|
|
|
return [sort @tables]; |
115
|
|
|
|
|
|
|
} |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
sub detect_unchanged { |
118
|
0
|
|
|
0
|
|
|
my ($full) = @_; |
119
|
0
|
|
|
|
|
|
my @unchanged; |
120
|
0
|
|
|
|
|
|
for my $table (@{$full}) { |
|
0
|
|
|
|
|
|
|
121
|
0
|
|
|
|
|
|
my $file = SQL_DIR."/$table.sql"; |
122
|
0
|
0
|
0
|
|
|
|
if (-f $file && mtime($file) == get_table_status($table, 'Update_time')) { |
123
|
0
|
|
|
|
|
|
push @unchanged, $table; |
124
|
|
|
|
|
|
|
} |
125
|
|
|
|
|
|
|
} |
126
|
0
|
|
|
|
|
|
return \@unchanged; |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
sub del_dumps_except { |
130
|
0
|
|
|
0
|
|
|
my ($incremental, $unchanged) = @_; |
131
|
0
|
|
|
|
|
|
my %incremental = map {$_=>1} @{$incremental}; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
132
|
0
|
|
|
|
|
|
my %unchanged = map {$_=>1} @{$unchanged}; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
133
|
0
|
|
|
|
|
|
for my $file (glob SQL_DIR.'/*.sql') { |
134
|
0
|
0
|
|
|
|
|
if ($file =~ m{\A\Q${\SQL_DIR}\E/([^.]*)[.]sql\z}xms) { |
|
0
|
0
|
|
|
|
|
|
135
|
0
|
|
|
|
|
|
my $table = $1; |
136
|
0
|
0
|
|
|
|
|
next if $unchanged{$table}; |
137
|
|
|
|
|
|
|
} |
138
|
0
|
|
|
|
|
|
elsif ($file =~ m{\A\Q${\SQL_DIR}\E/([^.]*)[.]\d+-(\d+)[.]sql\z}xms) { |
139
|
0
|
|
|
|
|
|
my $table = $1; |
140
|
0
|
0
|
0
|
|
|
|
next if $incremental{$table} |
141
|
|
|
|
|
|
|
&& mtime($file) >= get_table_status($table, 'Create_time'); |
142
|
|
|
|
|
|
|
} |
143
|
0
|
0
|
|
|
|
|
unlink $file or err "unlink($file): $!\n"; |
144
|
|
|
|
|
|
|
} |
145
|
0
|
|
|
|
|
|
return; |
146
|
|
|
|
|
|
|
} |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
sub dump_scheme_except { |
149
|
0
|
|
|
0
|
|
|
my ($ignore) = @_; |
150
|
0
|
|
|
|
|
|
my $db = get_config_line(CONFIG_DIR.'/db'); |
151
|
0
|
|
|
|
|
|
my $tables = join q{ }, map {"--ignore-table=\Q$db\E.\Q$_\E"} @{$ignore}; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
152
|
0
|
|
|
|
|
|
mysqldump("--opt -d $tables", SCHEME, time); |
153
|
0
|
|
|
|
|
|
return; |
154
|
|
|
|
|
|
|
} |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
sub dump_full { |
157
|
0
|
|
|
0
|
|
|
my ($full, $unchanged) = @_; |
158
|
0
|
|
|
|
|
|
my %unchanged = map {$_=>1} @{$unchanged}; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
159
|
0
|
|
|
|
|
|
for my $table (@{$full}) { |
|
0
|
|
|
|
|
|
|
160
|
0
|
0
|
|
|
|
|
next if $unchanged{$table}; |
161
|
0
|
|
|
|
|
|
my $file = SQL_DIR."/$table.sql"; |
162
|
0
|
|
|
|
|
|
my $t = get_table_status($table, 'Update_time'); |
163
|
0
|
|
|
|
|
|
mysqldump("--opt -t \Q$table\E", $file, $t); |
164
|
|
|
|
|
|
|
} |
165
|
0
|
|
|
|
|
|
return; |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
sub dump_incremental { |
169
|
0
|
|
|
0
|
|
|
my ($incremental) = @_; |
170
|
0
|
|
|
|
|
|
for my $table (@{$incremental}) { |
|
0
|
|
|
|
|
|
|
171
|
0
|
|
|
|
|
|
my $key = get_key($table); |
172
|
0
|
|
|
|
|
|
my $prev = max(0, map {m/-(\d+)[.]sql\z/xms} glob SQL_DIR."/\Q$table\E.*.sql"); |
|
0
|
|
|
|
|
|
|
173
|
0
|
|
|
|
|
|
my $next = get_table_status($table, 'Auto_increment'); |
174
|
0
|
0
|
|
|
|
|
if ($prev < $next-1) { |
175
|
0
|
|
|
|
|
|
my $from = $prev+1; |
176
|
0
|
|
|
|
|
|
my $to = $next-1; |
177
|
0
|
|
|
|
|
|
my $file = SQL_DIR."/$table.$from-$to.sql"; |
178
|
0
|
|
|
|
|
|
my $where= "$key>=$from AND $key<=$to"; |
179
|
0
|
|
|
|
|
|
my $t = get_table_status($table, 'Update_time'); |
180
|
0
|
|
|
|
|
|
mysqldump("--opt -t -w \Q$where\E \Q$table\E", $file, $t); |
181
|
|
|
|
|
|
|
} |
182
|
|
|
|
|
|
|
} |
183
|
0
|
|
|
|
|
|
return; |
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
### |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
sub get_table_status { |
189
|
0
|
|
|
0
|
|
|
my ($table, $field) = @_; |
190
|
|
|
|
|
|
|
my $val = $::dbh->selectrow_hashref('SHOW TABLE STATUS LIKE ?', |
191
|
0
|
|
|
|
|
|
undef, $table)->{$field}; |
192
|
0
|
0
|
|
|
|
|
if ($field =~ /_time\z/xms) { |
193
|
0
|
0
|
|
|
|
|
if (!defined $val) { |
194
|
0
|
|
|
|
|
|
$val = 0; |
195
|
|
|
|
|
|
|
} |
196
|
|
|
|
|
|
|
else { |
197
|
0
|
|
|
|
|
|
my @datetime = reverse split /\D+/xms, $val; |
198
|
0
|
|
|
|
|
|
$datetime[TIMELOCAL_MONTH]--; |
199
|
0
|
|
|
|
|
|
$val = timelocal(@datetime); |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
} |
202
|
0
|
|
|
|
|
|
return $val; |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
sub get_key { |
206
|
0
|
|
|
0
|
|
|
my ($table) = @_; |
207
|
0
|
|
|
|
|
|
my $desc = $::dbh->selectall_arrayref('DESC '.$table); |
208
|
|
|
|
|
|
|
err "First field in table $table must be: INT AUTO_INCREMENT PRIMARY KEY\n" |
209
|
0
|
|
|
|
|
|
if !(@{ $desc } |
210
|
|
|
|
|
|
|
&& $desc->[0][DESC_TYPE]=~/\A\w*int\b/xms |
211
|
|
|
|
|
|
|
&& $desc->[0][DESC_KEY] eq 'PRI' |
212
|
|
|
|
|
|
|
&& $desc->[0][DESC_EXTRA] eq 'auto_increment' |
213
|
0
|
0
|
0
|
|
|
|
&& 1 == grep {$_->[DESC_KEY] eq 'PRI'} @{$desc}); |
|
0
|
|
0
|
|
|
|
|
|
0
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
214
|
0
|
|
|
|
|
|
return $desc->[0][DESC_FIELD]; |
215
|
|
|
|
|
|
|
} |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
sub mtime { |
218
|
0
|
|
|
0
|
|
|
my ($file) = @_; |
219
|
0
|
|
|
|
|
|
return (stat $file)[STAT_MTIME]; |
220
|
|
|
|
|
|
|
} |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
sub mysqldump { |
223
|
0
|
|
|
0
|
|
|
my ($opt, $file, $t) = @_; |
224
|
0
|
0
|
|
|
|
|
system("$::MYSQLDUMP $opt > \Q$file\E.tmp") |
225
|
|
|
|
|
|
|
== 0 or err "system($::MYSQLDUMP $opt > $file.tmp): $?"; |
226
|
0
|
0
|
|
|
|
|
rename "$file.tmp", $file or err "rename($file.tmp, $file): $!"; |
227
|
0
|
0
|
|
|
|
|
utime $t, $t, $file or err "utime($file): $!"; |
228
|
0
|
|
|
|
|
|
return; |
229
|
|
|
|
|
|
|
} |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
1; # Magic true value required at end of module |
233
|
|
|
|
|
|
|
__END__ |