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