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