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