| 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__ |