1 #!/usr/local/bin/perl -w
6 ##### PLEASE CONFIGURE THIS SECTION ######
9 # Set this array for database authentication
10 my(@storage_sql_dsn) = (
11 'DBI:Pg:database=dbname;host=host.example.org',
15 # Set this to 1 if you are using the new database schema
16 # introduced with amavisd 2.4.0. Only do this if you also
17 # have the foreign key references between the tables and use
18 # time_iso as a real date type instead of a string.
19 my($new_dd) = 1; # 1 or undef
21 # Purge old messages - One Week
22 my($interval) = time - 7*24*60*60;
23 # Purge incomplete messages - 1 hour
24 my($partial_interval) = time - 60*60;
26 # These variables only matter if $new_dd is set to 1
27 # The values for these variables must be syntactically
28 # correct for your database. This value is passed to
29 # the 'interval' keyword. Please check your database
33 my($new_interval) = '1 week';
34 my($new_partial_interval) = '1 hour';
36 #my($new_interval) = '7 day';
37 #my($new_partial_interval) = '1 hour';
39 # PostgreSQL specific options
40 # Should we VACUUM ANALYZE the database after the purge?
41 # Default is undef because we should be using autovacuum
42 my($postop_vacuum) = undef; # 1 or undef
44 ##### END OF CONFIGURATION SECTION ######
52 my(@dsn) = split(/:/,$storage_sql_dsn[0],-1);
53 push(@modules, 'DBD::'.$dsn[1]) if uc($dsn[0]) eq 'DBI';
55 for my $m (@modules) {
57 $_ .= /^auto::/ ?
'.al' : '.pm' if !/\.(pm|pl|al)\z/;
59 eval { require $_ } or push(@missing, $m);
62 die "ERROR: MISSING module(s):\n" . join('', map { " $_\n" } @missing) if @missing;
64 sub build_queries
($) {
66 # Return a hash of queries to be run
70 'DELETE FROM msgrcpt ' .
74 "WHERE time_num < $partial_interval " .
75 'AND content IS NULL',
78 "WHERE time_num < $interval",
79 'del_msgs_mail_ids' =>
82 ' (SELECT 1 FROM msgrcpt ' .
83 ' WHERE msgrcpt.mail_id=msgs.mail_id)',
85 'DELETE FROM quarantine ' .
87 ' (SELECT 1 FROM msgs ' .
88 ' WHERE msgs.mail_id=quarantine.mail_id)',
90 'DELETE FROM msgrcpt ' .
92 ' (SELECT 1 FROM msgs ' .
93 ' WHERE msgs.mail_id=msgrcpt.mail_id)',
99 ' (SELECT DISTINCT mail_id ' .
100 ' FROM msgrcpt WHERE rs=\'D\')',
104 'DELETE FROM maddr ' .
105 'WHERE NOT EXISTS ' .
106 ' (SELECT sid FROM msgs WHERE sid=id) ' .
108 ' (SELECT rid FROM msgrcpt WHERE rid=id)'
112 if ($dbtype eq 'pgsql') {
113 $query{'vacuum_analyze'} = 'VACUUM ANALYZE';
115 $query{'del_old_mail_ids_new'} = 'DELETE FROM msgs ' .
116 'WHERE time_iso < now() ' .
117 "- interval '$new_interval'";
118 $query{'del_partial_msg_new'} = 'DELETE FROM msgs ' .
119 'WHERE time_iso < now() ' .
120 "- interval '$new_partial_interval' " .
121 ' AND content IS NULL';
124 if ($dbtype eq 'mysql') {
126 $query{'del_old_mail_ids_new'} = 'DELETE FROM msgs ' .
127 'WHERE time_iso < UTC_TIMESTAMP() ' .
128 "- interval $new_interval";
129 $query{'del_partial_msg_new'} = 'DELETE FROM msgs ' .
130 'WHERE time_iso < UTC_TIMESTAMP() ' .
131 "- interval $new_partial_interval " .
132 ' AND content IS NULL';
135 $query{'del_msgs_mail_ids'} = 'DELETE msgs FROM msgs ' .
136 'LEFT JOIN msgrcpt USING(mail_id) ' .
137 'WHERE msgrcpt.mail_id IS NULL';
138 $query{'del_quarantine'} = 'DELETE quarantine FROM quarantine ' .
139 'LEFT JOIN msgs USING(mail_id) '.
140 'WHERE msgs.mail_id IS NULL';
141 $query{'del_msgrcpt'} = 'DELETE msgrcpt FROM msgrcpt ' .
142 'LEFT JOIN msgs USING(mail_id) ' .
143 'WHERE msgs.mail_id IS NULL';
147 'vacuum_analyze' => 'VACUUM ANALYZE'
150 # Order of execution IS IMPORTANT!
151 my(@query_order) = qw
(del_d_flag del_partial_msg del_msgs_mail_ids
152 del_old_mail_ids del_quarantine del_msgrcpt
156 @query_order = qw
(del_d_flag_new del_partial_msg_new
157 del_old_mail_ids_new del_maddr
160 my(@post_query_order);
161 push(@post_query_order, 'vacuum_analyze') if $dbtype eq 'pgsql' && $postop_vacuum;
163 return (\
%query,\
@query_order,\
%post_query,\
@post_query_order);
169 print "\tmz_db_clean.pl [--verbose|-v] [--database|-db <dbtype>]\n";
170 print "\tmz_db_clean.pl --help|-h \n\n";
171 print "\tThe database configuration parameter is REQUIRED!\n\n";
172 print "\tPossible parameters for the \'--database\' option is \'mysql\'\n" .
178 Getopt
::Long
::Configure
('no_ignore_case');
179 GetOptions
(\
%opt, 'help|h', 'database|db=s', 'verbose|v',
182 usage
if not $opt{database
};
183 my($dbh) = connect_to_sql
(@storage_sql_dsn);
184 my($query,$query_order,$p_query,$p_query_order) = build_queries
($opt{database
});
185 my($sth_ref) = prepare_queries
($dbh,$query,$p_query);
186 my($result) = exec_queries
($dbh,$sth_ref,$query,$p_query,
187 $query_order,$p_query_order
189 print "Database cleanup successful\n" if $result;
193 sub exec_queries
($$$$$) {
194 my($dbh,$sth_ref,$query,$p_query,$query_o,$p_query_o) = @_;
199 foreach (@
$query_o) {
201 print "Executing... " . localtime() . "\n";
202 print $query->{$_} . "\n";
204 $affected = $sth_ref->{$_}->execute or die "Query '$_' did not execute";
205 print "$affected rows affected\n" if $opt{verbose
};
210 print "There was an error executing a query! $@\n" .
211 "No records modified by database maintenance\n" .
212 "Rollback complete.\n";
219 foreach (@
$p_query_o) {
221 print "Executing... " . localtime() . "\n";
222 print $p_query->{$_} . "\n";
224 $affected = $sth_ref->{$_}->execute or die "Query '$_' did not execute";
225 print "$affected rows affected\n" if $opt{verbose
};
229 print "There was an error executing an optional query! $@\n" .
237 sub connect_to_sql
(@
) {
239 my($dsn, $username, $password) = @sql_dsn;
240 print "Connecting to SQL database server\n" if $opt{verbose
};
241 print "Trying dsn '$dsn'\n" if $opt{verbose
};
242 my($dbh) = DBI
->connect($dsn, $username, $password,
243 # {PrintError => 1, RaiseError => 0, Taint => 1, AutoCommit => 0} );
244 {PrintError
=> 1, RaiseError
=> 0, Taint
=> 1} );
246 print "Connection to '$dsn' succeeded\n" if $opt{verbose
};
248 die "Unable to connect to '$dsn'!\n";
253 sub prepare_queries
($$$) {
256 my($p_query) = shift;
258 foreach my $query_set ($query, $p_query) {
259 foreach (keys %$query) {
260 $sths{$_} = $dbh->prepare($query->{$_});