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