add -c flag to skip cdr and h_cdr updates, RT#21464
[freeside.git] / FS / bin / freeside-upgrade
1 #!/usr/bin/perl -w
2
3 use strict;
4 use vars qw( $opt_d $opt_s $opt_q $opt_v $opt_r $opt_c );
5 use vars qw( $DEBUG $DRY_RUN );
6 use Getopt::Std;
7 use DBIx::DBSchema 0.31; #0.39
8 use FS::UID qw(adminsuidsetup checkeuid datasrc driver_name);
9 use FS::CurrentUser;
10 use FS::Schema qw( dbdef dbdef_dist reload_dbdef );
11 use FS::Misc::prune qw(prune_applications);
12 use FS::Conf;
13 use FS::Record qw(qsearch);
14 use FS::Upgrade qw(upgrade_schema upgrade_config upgrade upgrade_sqlradius);
15
16 my $start = time;
17
18 die "Not running uid freeside!" unless checkeuid();
19
20 getopts("dqrcs");
21
22 $DEBUG = !$opt_q;
23 #$DEBUG = $opt_v;
24
25 $DRY_RUN = $opt_d;
26
27 my $user = shift or die &usage;
28 $FS::CurrentUser::upgrade_hack = 1;
29 $FS::UID::callback_hack = 1;
30 my $dbh = adminsuidsetup($user);
31 $FS::UID::callback_hack = 0;
32
33 if ( driver_name =~ /^mysql/i ) { #until 0.39 is required above
34   eval "use DBIx::DBSchema 0.39;";
35   die $@ if $@;
36 }
37
38 #needs to match FS::Schema...
39 my $dbdef_file = "%%%FREESIDE_CONF%%%/dbdef.". datasrc;
40
41 dbdef_create($dbh, $dbdef_file);
42
43 delete $FS::Schema::dbdef_cache{$dbdef_file}; #force an actual reload
44 reload_dbdef($dbdef_file);
45
46 warn "Upgrade startup completed in ". (time-$start). " seconds\n"; # if $DEBUG;
47 $start = time;
48
49 #$DBIx::DBSchema::DEBUG = $DEBUG;
50 #$DBIx::DBSchema::Table::DEBUG = $DEBUG;
51 #$DBIx::DBSchema::Index::DEBUG = $DEBUG;
52
53 my @bugfix = ();
54
55 if (dbdef->table('cust_main')->column('agent_custid') && ! $opt_s) { 
56   push @bugfix,
57     "UPDATE cust_main SET agent_custid = NULL where agent_custid = ''";
58
59   push @bugfix,
60     "UPDATE h_cust_main SET agent_custid = NULL where agent_custid = ''"
61       if (dbdef->table('h_cust_main')); 
62 }
63
64 if ( dbdef->table('cgp_rule_condition') &&
65      dbdef->table('cgp_rule_condition')->column('condition') 
66    )
67 {
68   push @bugfix,
69    "ALTER TABLE ${_}cgp_rule_condition RENAME COLUMN condition TO conditionname"
70       for '', 'h_';
71
72 }
73
74 if ( dbdef->table('areacode') and
75      dbdef->table('areacode')->primary_key eq 'code' )
76 {
77   if ( driver_name =~ /^mysql/i ) {
78     push @bugfix, 
79       'ALTER TABLE areacode DROP PRIMARY KEY',
80       'ALTER TABLE areacode ADD COLUMN (areanum int auto_increment primary key)';
81   }
82   else {
83     push @bugfix, 'ALTER TABLE areacode DROP CONSTRAINT areacode_pkey';
84   }
85 }
86
87 if ( $DRY_RUN ) {
88   print
89     join(";\n", @bugfix ). ";\n";
90 } elsif ( @bugfix ) {
91
92   foreach my $statement ( @bugfix ) {
93     warn "$statement\n";
94     $dbh->do( $statement )
95       or die "Error: ". $dbh->errstr. "\n executing: $statement";
96   }
97
98   upgrade_schema();
99
100   dbdef_create($dbh, $dbdef_file);
101   delete $FS::Schema::dbdef_cache{$dbdef_file}; #force an actual reload
102   reload_dbdef($dbdef_file);
103
104 }
105
106 #you should have run fs-migrate-part_svc ages ago, when you upgraded
107 #from 1.3 to 1.4... if not, it needs to be hooked into -upgrade here or
108 #you'll lose all the part_svc settings it migrates to part_svc_column
109
110 my $conf = new FS::Conf;
111
112 my $dbdef_dist = dbdef_dist(
113   datasrc,
114   { 'queue-no_history' => $conf->exists('queue-no_history') },
115 );
116
117 my @statements = dbdef->sql_update_schema( $dbdef_dist,
118                                            $dbh,
119                                            { 'nullify_default' => 1, },
120                                          );
121
122 #### NEW CUSTOM FIELDS:
123 # 1. prevent new custom field columns from being dropped by upgrade
124 # 2. migrate old virtual fields to real fields (new custom fields)
125 ####
126 my $cfsth = $dbh->prepare("SELECT * FROM part_virtual_field") 
127                                                          or die $dbh->errstr;
128 $cfsth->execute or die $cfsth->errstr;
129 my $cf; 
130 while ( $cf = $cfsth->fetchrow_hashref ) {
131     my $tbl = $cf->{'dbtable'};
132     my $name = $cf->{'name'};
133     $name = lc($name) unless driver_name =~ /^mysql/i;
134
135     @statements = grep { $_ !~ /^\s*ALTER\s+TABLE\s+(h_|)$tbl\s+DROP\s+COLUMN\s+cf_$name\s*$/i }
136                                                                     @statements;
137     push @statements, 
138         "ALTER TABLE $tbl ADD COLUMN cf_$name varchar(".$cf->{'length'}.")"
139      unless (dbdef->table($tbl) && dbdef->table($tbl)->column("cf_$name"));
140     push @statements, 
141         "ALTER TABLE h_$tbl ADD COLUMN cf_$name varchar(".$cf->{'length'}.")"
142      unless (dbdef->table("h_$tbl") && dbdef->table("h_$tbl")->column("cf_$name"));
143 }
144 warn "Custom fields schema upgrade completed";
145
146 @statements = 
147   grep { $_ !~ /^CREATE +INDEX +h_queue/i } #useless, holds up queue insertion
148        @statements;
149
150 unless ( driver_name =~ /^mysql/i ) {
151   #not necessary under non-mysql, takes forever on big db
152   @statements =
153     grep { $_ !~ /^ *ALTER +TABLE +h_queue +ALTER +COLUMN +job +TYPE +varchar\(512\) *$/i }
154          @statements;
155 }
156
157 if ( $opt_c ) {
158   @statements =
159     grep { $_ !~ /^ *ALTER +TABLE +(h_)?cdr /i }
160          @statements;
161 }
162
163 if ( $DRY_RUN ) {
164   print
165     join(";\n", @statements ). ";\n";
166   exit;
167 } else {
168   foreach my $statement ( @statements ) {
169     warn "$statement\n";
170     $dbh->do( $statement )
171       or die "Error: ". $dbh->errstr. "\n executing: $statement";
172   }
173
174 #  warn "Pre-schema change upgrades completed in ". (time-$start). " seconds\n"; # if $DEBUG;
175 #  $start = time;
176
177 #  dbdef->update_schema( dbdef_dist(datasrc), $dbh );
178 }
179
180 warn "Schema upgrade completed in ". (time-$start). " seconds\n"; # if $DEBUG;
181 $start = time;
182
183 my $hashref = {};
184 $hashref->{dry_run} = 1 if $DRY_RUN;
185 $hashref->{debug} = 1 if $DEBUG && $DRY_RUN;
186 prune_applications($hashref) unless $opt_s;
187
188 warn "Application pruning completed in ". (time-$start). " seconds\n"; # if $DEBUG;
189 $start = time;
190
191 print "\n" if $DRY_RUN;
192
193 if ( $dbh->{Driver}->{Name} =~ /^mysql/i && ! $opt_s ) {
194
195   foreach my $table (qw( svc_acct svc_phone )) {
196
197     my $sth = $dbh->prepare(
198       "SELECT COUNT(*) FROM duplicate_lock WHERE lockname = '$table'"
199     ) or die $dbh->errstr;
200
201     $sth->execute or die $sth->errstr;
202
203     unless ( $sth->fetchrow_arrayref->[0] ) {
204
205       $sth = $dbh->prepare(
206         "INSERT INTO duplicate_lock ( lockname ) VALUES ( '$table' )"
207       ) or die $dbh->errstr;
208
209       $sth->execute or die $sth->errstr;
210
211     }
212
213   }
214
215   warn "Duplication lock creation completed in ". (time-$start). " seconds\n"; # if $DEBUG;
216   $start = time;
217
218 }
219
220 $dbh->commit or die $dbh->errstr;
221
222 dbdef_create($dbh, $dbdef_file);
223
224 $dbh->disconnect or die $dbh->errstr;
225
226 delete $FS::Schema::dbdef_cache{$dbdef_file}; #force an actual reload
227 $FS::UID::AutoCommit = 0;
228 $FS::UID::callback_hack = 1;
229 $dbh = adminsuidsetup($user);
230 $FS::UID::callback_hack = 0;
231 unless ( $DRY_RUN || $opt_s ) {
232   my $dir = "%%%FREESIDE_CONF%%%/conf.". datasrc;
233   if (!scalar(qsearch('conf', {}))) {
234     my $error = FS::Conf::init_config($dir);
235     if ($error) {
236       warn "CONFIGURATION UPGRADE FAILED\n";
237       $dbh->rollback or die $dbh->errstr;
238       die $error;
239     }
240   }
241 }
242 $dbh->commit or die $dbh->errstr;
243 $dbh->disconnect or die $dbh->errstr;
244
245 $FS::UID::AutoCommit = 1;
246
247 $dbh = adminsuidsetup($user);
248
249 warn "Re-initialization with updated schema completed in ". (time-$start). " seconds\n"; # if $DEBUG;
250 $start = time;
251
252 #### NEW CUSTOM FIELDS:
253 # 3. migrate old virtual field data to the new custom fields
254 ####
255 $cfsth = $dbh->prepare("SELECT * FROM virtual_field left join part_virtual_field using (vfieldpart)")
256                                                          or die $dbh->errstr;
257 $cfsth->execute or die $cfsth->errstr;
258 my @cfst;
259 while ( $cf = $cfsth->fetchrow_hashref ) {
260     my $tbl = $cf->{'dbtable'};
261     my $name = $cf->{'name'};
262     my $dtable = dbdef->table($tbl);
263     next unless $dtable && $dtable->primary_key; # XXX: warn first?
264     my $pkey = $dtable->primary_key;
265     next unless $dtable->column($pkey)->type =~ /int/i; # XXX: warn first?
266     push @cfst, "UPDATE $tbl set cf_$name = '".$cf->{'value'}."' WHERE $pkey = ".$cf->{'recnum'};
267     push @cfst, "DELETE FROM virtual_field WHERE vfieldnum = ".$cf->{'vfieldnum'};
268 }
269 foreach my $cfst ( @cfst ) {
270     warn "$cfst\n";
271     $dbh->do( $cfst )
272       or die "Error: ". $dbh->errstr. "\n executing: $cfst";
273 }
274 warn "Custom fields data upgrade completed";
275
276 upgrade_config()
277   unless $DRY_RUN || $opt_s;
278
279 $dbh->commit or die $dbh->errstr;
280
281 warn "Config updates completed in ". (time-$start). " seconds\n"; # if $DEBUG;
282 $start = time;
283
284 upgrade()
285   unless $DRY_RUN || $opt_s;
286
287 $dbh->commit or die $dbh->errstr;
288
289 warn "Table updates completed in ". (time-$start). " seconds\n"; # if $DEBUG;
290 $start = time;
291
292 upgrade_sqlradius()
293   unless $DRY_RUN || $opt_s || $opt_r;
294
295 warn "SQL RADIUS updates completed in ". (time-$start). " seconds\n"; # if $DEBUG;
296 $start = time;
297
298 $dbh->commit or die $dbh->errstr;
299 $dbh->disconnect or die $dbh->errstr;
300
301 warn "Final commit and disconnection completed in ". (time-$start). " seconds; upgrade done!\n"; # if $DEBUG;
302
303 ###
304
305 sub dbdef_create { # reverse engineer the schema from the DB and save to file
306   my( $dbh, $file ) = @_;
307   my $dbdef = new_native DBIx::DBSchema $dbh;
308   $dbdef->save($file);
309 }
310
311 sub usage {
312   die "Usage:\n  freeside-upgrade [ -d ] [ -r ] [ -s ] [ -q | -v ] user\n"; 
313 }
314
315 =head1 NAME
316
317 freeside-upgrade - Upgrades database schema for new freeside verisons.
318
319 =head1 SYNOPSIS
320
321   freeside-upgrade [ -d ] [ -r ] [ c ] [ -s ] [ -q | -v ]
322
323 =head1 DESCRIPTION
324
325 Reads your existing database schema and updates it to match the current schema,
326 adding any columns or tables necessary.
327
328 Also performs other upgrade functions:
329
330 =over 4
331
332 =item Calls FS:: Misc::prune::prune_applications (probably unnecessary every upgrade, but simply won't find any records to change)
333
334 =item If necessary, moves your configuration information from the filesystem in /usr/local/etc/freeside/conf.<datasrc> to the database.
335
336 =back
337
338   [ -d ]: Dry run; output SQL statements (to STDOUT) only, but do not execute
339           them.
340
341   [ -q ]: Run quietly.  This may become the default at some point.
342
343   [ -r ]: Skip sqlradius updates.  Useful for occassions where the sqlradius
344           databases may be inaccessible.
345
346   [ -c ]: Skip cdr and h_cdr updates.
347
348   [ -v ]: Run verbosely, sending debugging information to STDERR.  This is the
349           current default.
350
351   [ -s ]: Schema changes only.  Useful for Pg/slony slaves where the data
352           changes will be replicated from the Pg/slony master.
353
354 =head1 SEE ALSO
355
356 =cut
357