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