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
159   @statements =
160     grep { $_ !~ /^ *ALTER +TABLE +(h_)?cdr /i }
161          @statements;
162
163   @statements =
164     grep { $_ !~ /^ *CREATE +INDEX +(h_)?cdr\d+ /i }
165          @statements;
166
167 }
168
169 if ( $DRY_RUN ) {
170   print
171     join(";\n", @statements ). ";\n";
172   exit;
173 } else {
174   foreach my $statement ( @statements ) {
175     warn "$statement\n";
176     $dbh->do( $statement )
177       or die "Error: ". $dbh->errstr. "\n executing: $statement";
178   }
179
180 #  warn "Pre-schema change upgrades completed in ". (time-$start). " seconds\n"; # if $DEBUG;
181 #  $start = time;
182
183 #  dbdef->update_schema( dbdef_dist(datasrc), $dbh );
184 }
185
186 warn "Schema upgrade completed in ". (time-$start). " seconds\n"; # if $DEBUG;
187 $start = time;
188
189 my $hashref = {};
190 $hashref->{dry_run} = 1 if $DRY_RUN;
191 $hashref->{debug} = 1 if $DEBUG && $DRY_RUN;
192 prune_applications($hashref) unless $opt_s;
193
194 warn "Application pruning completed in ". (time-$start). " seconds\n"; # if $DEBUG;
195 $start = time;
196
197 print "\n" if $DRY_RUN;
198
199 if ( $dbh->{Driver}->{Name} =~ /^mysql/i && ! $opt_s ) {
200
201   foreach my $table (qw( svc_acct svc_phone )) {
202
203     my $sth = $dbh->prepare(
204       "SELECT COUNT(*) FROM duplicate_lock WHERE lockname = '$table'"
205     ) or die $dbh->errstr;
206
207     $sth->execute or die $sth->errstr;
208
209     unless ( $sth->fetchrow_arrayref->[0] ) {
210
211       $sth = $dbh->prepare(
212         "INSERT INTO duplicate_lock ( lockname ) VALUES ( '$table' )"
213       ) or die $dbh->errstr;
214
215       $sth->execute or die $sth->errstr;
216
217     }
218
219   }
220
221   warn "Duplication lock creation completed in ". (time-$start). " seconds\n"; # if $DEBUG;
222   $start = time;
223
224 }
225
226 $dbh->commit or die $dbh->errstr;
227
228 dbdef_create($dbh, $dbdef_file);
229
230 $dbh->disconnect or die $dbh->errstr;
231
232 delete $FS::Schema::dbdef_cache{$dbdef_file}; #force an actual reload
233 $FS::UID::AutoCommit = 0;
234 $FS::UID::callback_hack = 1;
235 $dbh = adminsuidsetup($user);
236 $FS::UID::callback_hack = 0;
237 unless ( $DRY_RUN || $opt_s ) {
238   my $dir = "%%%FREESIDE_CONF%%%/conf.". datasrc;
239   if (!scalar(qsearch('conf', {}))) {
240     my $error = FS::Conf::init_config($dir);
241     if ($error) {
242       warn "CONFIGURATION UPGRADE FAILED\n";
243       $dbh->rollback or die $dbh->errstr;
244       die $error;
245     }
246   }
247 }
248 $dbh->commit or die $dbh->errstr;
249 $dbh->disconnect or die $dbh->errstr;
250
251 $FS::UID::AutoCommit = 1;
252
253 $dbh = adminsuidsetup($user);
254
255 warn "Re-initialization with updated schema completed in ". (time-$start). " seconds\n"; # if $DEBUG;
256 $start = time;
257
258 #### NEW CUSTOM FIELDS:
259 # 3. migrate old virtual field data to the new custom fields
260 ####
261 $cfsth = $dbh->prepare("SELECT * FROM virtual_field left join part_virtual_field using (vfieldpart)")
262                                                          or die $dbh->errstr;
263 $cfsth->execute or die $cfsth->errstr;
264 my @cfst;
265 while ( $cf = $cfsth->fetchrow_hashref ) {
266     my $tbl = $cf->{'dbtable'};
267     my $name = $cf->{'name'};
268     my $dtable = dbdef->table($tbl);
269     next unless $dtable && $dtable->primary_key; # XXX: warn first?
270     my $pkey = $dtable->primary_key;
271     next unless $dtable->column($pkey)->type =~ /int/i; # XXX: warn first?
272     push @cfst, "UPDATE $tbl set cf_$name = '".$cf->{'value'}."' WHERE $pkey = ".$cf->{'recnum'};
273     push @cfst, "DELETE FROM virtual_field WHERE vfieldnum = ".$cf->{'vfieldnum'};
274 }
275 foreach my $cfst ( @cfst ) {
276     warn "$cfst\n";
277     $dbh->do( $cfst )
278       or die "Error: ". $dbh->errstr. "\n executing: $cfst";
279 }
280 warn "Custom fields data upgrade completed";
281
282 upgrade_config()
283   unless $DRY_RUN || $opt_s;
284
285 $dbh->commit or die $dbh->errstr;
286
287 warn "Config updates completed in ". (time-$start). " seconds\n"; # if $DEBUG;
288 $start = time;
289
290 upgrade()
291   unless $DRY_RUN || $opt_s;
292
293 $dbh->commit or die $dbh->errstr;
294
295 warn "Table updates completed in ". (time-$start). " seconds\n"; # if $DEBUG;
296 $start = time;
297
298 upgrade_sqlradius()
299   unless $DRY_RUN || $opt_s || $opt_r;
300
301 warn "SQL RADIUS updates completed in ". (time-$start). " seconds\n"; # if $DEBUG;
302 $start = time;
303
304 $dbh->commit or die $dbh->errstr;
305 $dbh->disconnect or die $dbh->errstr;
306
307 warn "Final commit and disconnection completed in ". (time-$start). " seconds; upgrade done!\n"; # if $DEBUG;
308
309 ###
310
311 sub dbdef_create { # reverse engineer the schema from the DB and save to file
312   my( $dbh, $file ) = @_;
313   my $dbdef = new_native DBIx::DBSchema $dbh;
314   $dbdef->save($file);
315 }
316
317 sub usage {
318   die "Usage:\n  freeside-upgrade [ -d ] [ -r ] [ -s ] [ -q | -v ] user\n"; 
319 }
320
321 =head1 NAME
322
323 freeside-upgrade - Upgrades database schema for new freeside verisons.
324
325 =head1 SYNOPSIS
326
327   freeside-upgrade [ -d ] [ -r ] [ c ] [ -s ] [ -q | -v ]
328
329 =head1 DESCRIPTION
330
331 Reads your existing database schema and updates it to match the current schema,
332 adding any columns or tables necessary.
333
334 Also performs other upgrade functions:
335
336 =over 4
337
338 =item Calls FS:: Misc::prune::prune_applications (probably unnecessary every upgrade, but simply won't find any records to change)
339
340 =item If necessary, moves your configuration information from the filesystem in /usr/local/etc/freeside/conf.<datasrc> to the database.
341
342 =back
343
344   [ -d ]: Dry run; output SQL statements (to STDOUT) only, but do not execute
345           them.
346
347   [ -q ]: Run quietly.  This may become the default at some point.
348
349   [ -r ]: Skip sqlradius updates.  Useful for occassions where the sqlradius
350           databases may be inaccessible.
351
352   [ -c ]: Skip cdr and h_cdr updates.
353
354   [ -v ]: Run verbosely, sending debugging information to STDERR.  This is the
355           current default.
356
357   [ -s ]: Schema changes only.  Useful for Pg/slony slaves where the data
358           changes will be replicated from the Pg/slony master.
359
360 =head1 SEE ALSO
361
362 =cut
363