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