1199 lines
41 KiB
Perl
1199 lines
41 KiB
Perl
|
|
#!/usr/bin/perl
|
||
|
|
# This code was forked from the LiveJournal project owned and operated
|
||
|
|
# by Live Journal, Inc. The code has been modified and expanded by
|
||
|
|
# Dreamwidth Studios, LLC. These files were originally licensed under
|
||
|
|
# the terms of the license supplied by Live Journal, Inc, which can
|
||
|
|
# currently be found at:
|
||
|
|
#
|
||
|
|
# http://code.livejournal.org/trac/livejournal/browser/trunk/LICENSE-LiveJournal.txt
|
||
|
|
#
|
||
|
|
# In accordance with the original license, this code and all its
|
||
|
|
# modifications are provided under the GNU General Public License.
|
||
|
|
# A copy of that license can be found in the LICENSE file included as
|
||
|
|
# part of this distribution.
|
||
|
|
#
|
||
|
|
# This program will bring your LiveJournal database schema up-to-date
|
||
|
|
#
|
||
|
|
|
||
|
|
use strict;
|
||
|
|
|
||
|
|
BEGIN { $LJ::_T_CONFIG = "$ENV{DW_TEST}"; require "$ENV{LJHOME}/cgi-bin/ljlib.pl"; }
|
||
|
|
use Digest::MD5;
|
||
|
|
use Getopt::Long;
|
||
|
|
use File::Path ();
|
||
|
|
use File::Basename qw/ dirname /;
|
||
|
|
use File::Copy ();
|
||
|
|
use Cwd qw/ abs_path /;
|
||
|
|
use Image::Size ();
|
||
|
|
use LJ::S2;
|
||
|
|
|
||
|
|
my $opt_sql = 0;
|
||
|
|
my $opt_drop = 0;
|
||
|
|
my $opt_pop = 0;
|
||
|
|
my $opt_confirm = "";
|
||
|
|
my $opt_skip = "";
|
||
|
|
my $opt_help = 0;
|
||
|
|
my $cluster = 0; # by default, upgrade master.
|
||
|
|
my $opt_listtables;
|
||
|
|
my $opt_nostyles;
|
||
|
|
my $opt_forcebuild = 0;
|
||
|
|
my $opt_compiletodisk = 0;
|
||
|
|
my $opt_innodb;
|
||
|
|
my $opt_poptest = 0;
|
||
|
|
my $opt_parallel = 0;
|
||
|
|
|
||
|
|
exit 1
|
||
|
|
unless GetOptions(
|
||
|
|
"runsql" => \$opt_sql,
|
||
|
|
"drop" => \$opt_drop,
|
||
|
|
"populate|p" => \$opt_pop,
|
||
|
|
"confirm=s" => \$opt_confirm,
|
||
|
|
"cluster=s" => \$cluster,
|
||
|
|
"skip=s" => \$opt_skip,
|
||
|
|
"help" => \$opt_help,
|
||
|
|
"listtables" => \$opt_listtables,
|
||
|
|
"nostyles" => \$opt_nostyles,
|
||
|
|
"forcebuild|fb" => \$opt_forcebuild,
|
||
|
|
"ctd" => \$opt_compiletodisk,
|
||
|
|
"innodb" => \$opt_innodb,
|
||
|
|
"parallel=i" => \$opt_parallel,
|
||
|
|
);
|
||
|
|
|
||
|
|
$opt_nostyles = 1 unless LJ::is_enabled("update_styles");
|
||
|
|
$opt_nostyles = 1 if $ENV{DW_TEST};
|
||
|
|
$opt_innodb = 1;
|
||
|
|
|
||
|
|
if ($opt_help) {
|
||
|
|
die "Usage: update-db.pl
|
||
|
|
-r --runsql Actually do the SQL, instead of just showing it.
|
||
|
|
-p --populate Populate the database with the latest required base data.
|
||
|
|
-d --drop Drop old unused tables (default is to never)
|
||
|
|
--cluster=<n> Upgrade cluster number <n> (defaut,0 is global cluster)
|
||
|
|
--cluster=<n>,<n>,<n>
|
||
|
|
--cluster=user Update user clusters
|
||
|
|
--cluster=all Update user clusters, and global
|
||
|
|
-l --listtables Print used tables, one per line.
|
||
|
|
--nostyles When used in combination with --populate, disables population
|
||
|
|
of style information.
|
||
|
|
--innodb Use InnoDB when creating tables.
|
||
|
|
--parallel=<n> Number of parallel processes for S2 compilation (default: 8)
|
||
|
|
Set to 1 to disable parallelization and use original serial behavior.
|
||
|
|
--forcebuild Force recompilation of ALL S2 layers, ignoring MD5 source checks.
|
||
|
|
This will rebuild all system layers from scratch.
|
||
|
|
";
|
||
|
|
}
|
||
|
|
|
||
|
|
## make sure $LJHOME is set so we can load & run everything
|
||
|
|
unless ( -d $ENV{'LJHOME'} ) {
|
||
|
|
die "LJHOME environment variable is not set, or is not a directory.\n"
|
||
|
|
. "You must fix this before you can run this database update script.";
|
||
|
|
}
|
||
|
|
|
||
|
|
die "Can't --populate a cluster" if $opt_pop && ( $cluster && $cluster ne "all" );
|
||
|
|
|
||
|
|
my @clusters;
|
||
|
|
foreach my $cl ( split( /,/, $cluster ) ) {
|
||
|
|
die "Invalid cluster spec: $cl\n"
|
||
|
|
unless $cl =~ /^\s*((\d+)|all|user)\s*$/;
|
||
|
|
if ( $cl eq "all" ) { push @clusters, 0, @LJ::CLUSTERS; }
|
||
|
|
elsif ( $cl eq "user" ) { push @clusters, @LJ::CLUSTERS; }
|
||
|
|
else { push @clusters, $1; }
|
||
|
|
}
|
||
|
|
@clusters = (0) unless @clusters;
|
||
|
|
|
||
|
|
my $su; # system user, not available until populate mode
|
||
|
|
my %status; # clusterid -> string
|
||
|
|
my %clustered_table; # $table -> 1
|
||
|
|
my $sth;
|
||
|
|
my %table_exists; # $table -> 1
|
||
|
|
my %table_unknown; # $table -> 1
|
||
|
|
my %table_create; # $table -> $create_sql
|
||
|
|
my %table_drop; # $table -> 1
|
||
|
|
my %table_status; # $table -> { SHOW TABLE STATUS ... row }
|
||
|
|
my %post_create; # $table -> [ [ $action, $what ]* ]
|
||
|
|
my %coltype; # $table -> { $col -> $type }
|
||
|
|
my %coldefault; # $table -> { $col -> $default }
|
||
|
|
my %indexname; # $table -> "INDEX"|"UNIQUE" . ":" . "col1-col2-col3" -> "PRIMARY" | index_name
|
||
|
|
my @alters;
|
||
|
|
my $dbh;
|
||
|
|
|
||
|
|
CLUSTER: foreach my $cluster (@clusters) {
|
||
|
|
print "Updating cluster: $cluster\n" unless $opt_listtables;
|
||
|
|
## make sure we can connect
|
||
|
|
$dbh = $cluster ? LJ::get_cluster_master($cluster) : LJ::get_db_writer();
|
||
|
|
unless ($dbh) {
|
||
|
|
$status{$cluster} =
|
||
|
|
"ERROR: Can't connect to the database (clust\#$cluster), so I can't update it. ("
|
||
|
|
. DBI->errstr . ")";
|
||
|
|
next CLUSTER;
|
||
|
|
}
|
||
|
|
|
||
|
|
# reset everything
|
||
|
|
%clustered_table = %table_exists = %table_unknown =
|
||
|
|
%table_create = %table_drop = %post_create = %coltype = %indexname = %table_status = ();
|
||
|
|
@alters = ();
|
||
|
|
|
||
|
|
## figure out what tables already exist (but not details of their structure)
|
||
|
|
$sth = $dbh->prepare("SHOW TABLES");
|
||
|
|
$sth->execute;
|
||
|
|
while ( my ($table) = $sth->fetchrow_array ) {
|
||
|
|
next if $table =~ /^(access|errors)\d+$/;
|
||
|
|
$table_exists{$table} = 1;
|
||
|
|
}
|
||
|
|
%table_unknown = %table_exists; # for now, later we'll delete from table_unknown
|
||
|
|
|
||
|
|
## very important that local is run first! (it can define tables that
|
||
|
|
## the site-wide would drop if it didn't know about them already)
|
||
|
|
|
||
|
|
my $load_datfile = sub {
|
||
|
|
my $file = shift;
|
||
|
|
my $local = shift;
|
||
|
|
return if $local && !-e $file;
|
||
|
|
open( F, $file ) or die "Can't find database update file at $file\n";
|
||
|
|
my $data;
|
||
|
|
{
|
||
|
|
local $/ = undef;
|
||
|
|
$data = <F>;
|
||
|
|
}
|
||
|
|
close F;
|
||
|
|
eval $data;
|
||
|
|
die "Can't run $file: $@\n" if $@;
|
||
|
|
return 1;
|
||
|
|
};
|
||
|
|
|
||
|
|
foreach my $fn ( LJ::get_all_files("bin/upgrading/update-db-local.pl") ) {
|
||
|
|
$load_datfile->( $fn, 1 );
|
||
|
|
}
|
||
|
|
foreach my $fn ( LJ::get_all_files("bin/upgrading/update-db-general.pl") ) {
|
||
|
|
$load_datfile->($fn);
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach my $t ( sort keys %table_create ) {
|
||
|
|
delete $table_drop{$t} if ( $table_drop{$t} );
|
||
|
|
print "$t\n" if $opt_listtables;
|
||
|
|
}
|
||
|
|
exit if $opt_listtables;
|
||
|
|
|
||
|
|
foreach my $t ( keys %table_drop ) {
|
||
|
|
delete $table_unknown{$t};
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach my $t ( keys %table_unknown ) {
|
||
|
|
print "# Warning: unknown live table: $t\n";
|
||
|
|
}
|
||
|
|
|
||
|
|
my $run_alter = $table_exists{dbnotes};
|
||
|
|
|
||
|
|
## create tables
|
||
|
|
foreach my $t ( keys %table_create ) {
|
||
|
|
next if $table_exists{$t};
|
||
|
|
create_table($t);
|
||
|
|
}
|
||
|
|
|
||
|
|
## drop tables
|
||
|
|
foreach my $t ( keys %table_drop ) {
|
||
|
|
next unless $table_exists{$t};
|
||
|
|
drop_table($t);
|
||
|
|
}
|
||
|
|
|
||
|
|
# If dbnotes didn't exist before but was just created, we can now run alters
|
||
|
|
if ( !$run_alter && $opt_sql ) {
|
||
|
|
my ($exists) = $dbh->selectrow_array("SHOW TABLES LIKE 'dbnotes'");
|
||
|
|
if ($exists) {
|
||
|
|
print "## dbnotes table was just created, running alters.\n";
|
||
|
|
$run_alter = 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($run_alter) {
|
||
|
|
## do all the alters
|
||
|
|
foreach my $s (@alters) {
|
||
|
|
$s->( $dbh, $opt_sql );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
print
|
||
|
|
"## Skipping alters this pass, re-run with -r to create tables and then run alters.\n";
|
||
|
|
}
|
||
|
|
|
||
|
|
$status{$cluster} = "OKAY";
|
||
|
|
}
|
||
|
|
|
||
|
|
print "\ncluster: status\n";
|
||
|
|
foreach my $clid ( sort { $a <=> $b } keys %status ) {
|
||
|
|
printf "%7d: %s\n", $clid, $status{$clid};
|
||
|
|
}
|
||
|
|
print "\n";
|
||
|
|
|
||
|
|
if ($opt_pop) {
|
||
|
|
$dbh = LJ::get_db_writer()
|
||
|
|
or die "Couldn't get master handle for population.";
|
||
|
|
populate_database();
|
||
|
|
}
|
||
|
|
|
||
|
|
print "# Done.\n";
|
||
|
|
|
||
|
|
############################################################################
|
||
|
|
|
||
|
|
sub populate_database {
|
||
|
|
populate_basedata();
|
||
|
|
populate_proplists();
|
||
|
|
|
||
|
|
# system user
|
||
|
|
my $made_system;
|
||
|
|
( $su, $made_system ) = vivify_system_user();
|
||
|
|
|
||
|
|
populate_moods();
|
||
|
|
populate_external_moods();
|
||
|
|
|
||
|
|
# we have a flag to disable population of s1/s2 if the user requests
|
||
|
|
unless ($opt_nostyles) {
|
||
|
|
populate_s2();
|
||
|
|
}
|
||
|
|
|
||
|
|
print
|
||
|
|
"\nThe system user was created with a random password.\nRun \$LJHOME/bin/upgrading/make_system.pl to change its password and grant the necessary privileges."
|
||
|
|
if $made_system;
|
||
|
|
|
||
|
|
print "\nRemember to also run:\n bin/upgrading/texttool.pl load\n\n"
|
||
|
|
if $LJ::IS_DEV_SERVER;
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
sub vivify_system_user {
|
||
|
|
my $freshly_made = 0;
|
||
|
|
my $su = LJ::load_user("system");
|
||
|
|
unless ($su) {
|
||
|
|
print "System user not found. Creating with random password.\n";
|
||
|
|
my $pass = LJ::make_auth_code(10);
|
||
|
|
$su = LJ::User->create(
|
||
|
|
user => 'system',
|
||
|
|
name => 'System Account',
|
||
|
|
password => $pass
|
||
|
|
);
|
||
|
|
die "Failed to create system user." unless $su;
|
||
|
|
$freshly_made = 1;
|
||
|
|
}
|
||
|
|
return wantarray ? ( $su, $freshly_made ) : $su;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub populate_s2 {
|
||
|
|
|
||
|
|
# S2
|
||
|
|
print "Populating public system styles (S2):\n";
|
||
|
|
if ($opt_forcebuild) {
|
||
|
|
print "*** FORCE REBUILD MODE: All layers will be rebuilt regardless of changes ***\n";
|
||
|
|
}
|
||
|
|
{
|
||
|
|
my $sysid = $su->{'userid'};
|
||
|
|
|
||
|
|
# find existing re-distributed layers that are in the database
|
||
|
|
# and their styleids.
|
||
|
|
my $existing = LJ::S2::get_public_layers( { force => 1 }, $sysid );
|
||
|
|
|
||
|
|
my %known_id;
|
||
|
|
chdir "$ENV{'LJHOME'}/" or die;
|
||
|
|
my %layer; # maps redist_uniq -> { 'type', 'parent' (uniq), 'id' (s2lid) }
|
||
|
|
|
||
|
|
my $has_new_layer = 0;
|
||
|
|
my @layers_to_compile; # Array to store layer compilation jobs
|
||
|
|
|
||
|
|
# If force building, we definitely have "new" layers for cache clearing purposes
|
||
|
|
$has_new_layer = 1 if $opt_forcebuild;
|
||
|
|
my $compile = sub {
|
||
|
|
my ( $base, $type, $parent, $s2source, $LD ) = @_;
|
||
|
|
return unless $s2source =~ /\S/;
|
||
|
|
|
||
|
|
my $id = $existing->{$base} ? $existing->{$base}->{'s2lid'} : 0;
|
||
|
|
unless ($id) {
|
||
|
|
my $parentid = 0;
|
||
|
|
$parentid = $layer{$parent}->{'id'} unless $type eq "core";
|
||
|
|
|
||
|
|
# allocate a new one.
|
||
|
|
$dbh->do(
|
||
|
|
"INSERT INTO s2layers (s2lid, b2lid, userid, type) "
|
||
|
|
. "VALUES (NULL, $parentid, $sysid, ?)",
|
||
|
|
undef, $type
|
||
|
|
);
|
||
|
|
die $dbh->errstr if $dbh->err;
|
||
|
|
$id = $dbh->{'mysql_insertid'};
|
||
|
|
if ($id) {
|
||
|
|
$dbh->do(
|
||
|
|
"INSERT INTO s2info (s2lid, infokey, value) VALUES (?,'redist_uniq',?)",
|
||
|
|
undef, $id, $base );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
die "Can't generate ID for '$base'" unless $id;
|
||
|
|
|
||
|
|
# remember it so we don't delete it later.
|
||
|
|
$known_id{$id} = 1;
|
||
|
|
|
||
|
|
$layer{$base} = {
|
||
|
|
'type' => $type,
|
||
|
|
'parent' => $parent,
|
||
|
|
'id' => $id,
|
||
|
|
};
|
||
|
|
|
||
|
|
my $parid = $layer{$parent}->{'id'};
|
||
|
|
|
||
|
|
# see if source changed
|
||
|
|
my $md5_source = Digest::MD5::md5_hex($s2source);
|
||
|
|
my $source_exist = LJ::S2::load_layer_source($id);
|
||
|
|
my $md5_exist = Digest::MD5::md5_hex($source_exist);
|
||
|
|
|
||
|
|
$has_new_layer = 1 unless $source_exist;
|
||
|
|
$has_new_layer = 1 if $opt_forcebuild; # Force cache clearing when force building
|
||
|
|
|
||
|
|
# skip compilation if source is unchanged and parent wasn't rebuilt and not forcing rebuild
|
||
|
|
return if $md5_source eq $md5_exist && !$layer{$parent}->{'built'} && !$opt_forcebuild;
|
||
|
|
|
||
|
|
print "$base($id) is $type";
|
||
|
|
if ($parid) { print ", parent = $parent($parid)"; }
|
||
|
|
if ($opt_forcebuild) { print " [FORCE REBUILD]"; }
|
||
|
|
print "\n";
|
||
|
|
|
||
|
|
# we're going to go ahead and build it.
|
||
|
|
$layer{$base}->{'built'} = 1;
|
||
|
|
|
||
|
|
# Store compilation job for parallel processing
|
||
|
|
push @layers_to_compile,
|
||
|
|
{
|
||
|
|
base => $base,
|
||
|
|
id => $id,
|
||
|
|
sysid => $sysid,
|
||
|
|
parid => $parid,
|
||
|
|
type => $type,
|
||
|
|
s2source => $s2source,
|
||
|
|
LD => $LD,
|
||
|
|
};
|
||
|
|
};
|
||
|
|
|
||
|
|
# Function to compile a single layer (runs in child process)
|
||
|
|
my $compile_layer = sub {
|
||
|
|
my $job = shift;
|
||
|
|
|
||
|
|
# Since we might fork, we disconnect here and then people can get a new one.
|
||
|
|
LJ::DB::disconnect_dbs();
|
||
|
|
|
||
|
|
# Fork out a child so it can compile. This saves us the memory usage.
|
||
|
|
if ( my $pid = fork ) {
|
||
|
|
return $pid; # Return the PID to parent
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
# Child process
|
||
|
|
$dbh = LJ::get_db_writer();
|
||
|
|
|
||
|
|
# compile!
|
||
|
|
my $lay = {
|
||
|
|
's2lid' => $job->{id},
|
||
|
|
'userid' => $job->{sysid},
|
||
|
|
'b2lid' => $job->{parid},
|
||
|
|
'type' => $job->{type},
|
||
|
|
};
|
||
|
|
my $error = "";
|
||
|
|
my $compiled;
|
||
|
|
my $info;
|
||
|
|
|
||
|
|
# do this in an eval, so that if the layer_compile call returns an error,
|
||
|
|
# we die and pass it up in $@. but if layer_compile dies, it should pass up
|
||
|
|
# an error itself, which we can get.
|
||
|
|
eval {
|
||
|
|
die $error
|
||
|
|
unless LJ::S2::layer_compile(
|
||
|
|
$lay,
|
||
|
|
\$error,
|
||
|
|
{
|
||
|
|
's2ref' => \$job->{s2source},
|
||
|
|
'redist_uniq' => $job->{base},
|
||
|
|
'compiledref' => \$compiled,
|
||
|
|
'layerinfo' => \$info,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
if ($@) {
|
||
|
|
print "S2 compilation failed: $@\n";
|
||
|
|
exit 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($opt_compiletodisk) {
|
||
|
|
open( CO, ">$job->{LD}/$job->{base}.pl" ) or die;
|
||
|
|
print CO $compiled;
|
||
|
|
close CO;
|
||
|
|
}
|
||
|
|
|
||
|
|
# put raw S2 in database.
|
||
|
|
LJ::S2::set_layer_source( $job->{id}, \$job->{s2source} );
|
||
|
|
|
||
|
|
# We are the child, so we can exit here.
|
||
|
|
exit;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
my @layerfiles = LJ::get_all_files( "styles/s2layers.dat", home_first => 1 );
|
||
|
|
while (@layerfiles) {
|
||
|
|
my $file = abs_path( shift @layerfiles );
|
||
|
|
next unless -e $file;
|
||
|
|
open( SL, $file ) or die;
|
||
|
|
my $LD = dirname($file);
|
||
|
|
my $d_file = $file;
|
||
|
|
my $d_LD = $LD;
|
||
|
|
|
||
|
|
$d_file =~ s!^\Q$LJ::HOME\E/*!!;
|
||
|
|
$d_LD =~ s!^\Q$LJ::HOME\E/*!!;
|
||
|
|
|
||
|
|
print "SOURCE: $d_file ( $d_LD )\n";
|
||
|
|
|
||
|
|
while (<SL>) {
|
||
|
|
s/\#.*//;
|
||
|
|
s/^\s+//;
|
||
|
|
s/\s+$//;
|
||
|
|
next unless /\S/;
|
||
|
|
my ( $base, $type, $parent ) = split;
|
||
|
|
|
||
|
|
if ( $type eq "INCLUDE" ) {
|
||
|
|
unshift @layerfiles, dirname($file) . "/$base";
|
||
|
|
next;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $type ne "core" && !defined $layer{$parent} ) {
|
||
|
|
die "'$base' references unknown parent '$parent'\n";
|
||
|
|
}
|
||
|
|
|
||
|
|
# is the referenced $base file really an aggregation of
|
||
|
|
# many smaller layers? (likely themes, which tend to be small)
|
||
|
|
my $multi = ( $type =~ s/\+$// );
|
||
|
|
|
||
|
|
my $s2source;
|
||
|
|
open( L, "$LD/$base.s2" ) or die "Can't open file: $base.s2\n";
|
||
|
|
|
||
|
|
unless ($multi) {
|
||
|
|
|
||
|
|
# check if this layer should be mapped to another layer (i.e. exact copy except for layerinfo)
|
||
|
|
if ( $type =~ s/\(([^)]+)\)// )
|
||
|
|
{ # grab the layer in the parentheses and erase it
|
||
|
|
open( my $map_layout, "$LD/$1.s2" ) or die "Can't open file: $1.s2\n";
|
||
|
|
while (<$map_layout>) { $s2source .= $_; }
|
||
|
|
}
|
||
|
|
while (<L>) { $s2source .= $_; }
|
||
|
|
$compile->( $base, $type, $parent, $s2source, $LD );
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
my $curname;
|
||
|
|
while (<L>) {
|
||
|
|
if (/^\#NEWLAYER:\s*(\S+)/) {
|
||
|
|
my $newname = $1;
|
||
|
|
$compile->( $curname, $type, $parent, $s2source, $LD );
|
||
|
|
$curname = $newname;
|
||
|
|
$s2source = "";
|
||
|
|
}
|
||
|
|
elsif (/^\#NEWLAYER/) {
|
||
|
|
die "Badly formatted \#NEWLAYER line";
|
||
|
|
}
|
||
|
|
elsif ($curname) {
|
||
|
|
$s2source .= $_;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
# skip any lines before the first #NEWLAYER section
|
||
|
|
}
|
||
|
|
}
|
||
|
|
$compile->( $curname, $type, $parent, $s2source, $LD );
|
||
|
|
}
|
||
|
|
close L;
|
||
|
|
}
|
||
|
|
close SL;
|
||
|
|
}
|
||
|
|
|
||
|
|
# Now process all compilation jobs in dependency order with parallelization
|
||
|
|
if (@layers_to_compile) {
|
||
|
|
print "\nCompiling " . scalar(@layers_to_compile) . " layers in parallel...\n";
|
||
|
|
|
||
|
|
# Build dependency graph and compile in topological order
|
||
|
|
my %dependencies; # base -> [list of dependencies]
|
||
|
|
my %rdependencies; # base -> [list of things that depend on this]
|
||
|
|
my %pending_jobs; # base -> job
|
||
|
|
my %completed; # base -> 1 when done
|
||
|
|
|
||
|
|
# Build dependency mapping
|
||
|
|
for my $job (@layers_to_compile) {
|
||
|
|
my $base = $job->{base};
|
||
|
|
$pending_jobs{$base} = $job;
|
||
|
|
$dependencies{$base} = [];
|
||
|
|
$rdependencies{$base} = [];
|
||
|
|
}
|
||
|
|
|
||
|
|
# Now build dependencies after all jobs are in the pending_jobs hash
|
||
|
|
for my $job (@layers_to_compile) {
|
||
|
|
my $base = $job->{base};
|
||
|
|
my $parent = $layer{$base}{parent};
|
||
|
|
|
||
|
|
# Add dependency if parent exists and is not a core layer
|
||
|
|
if ( $parent && $parent ne '-' ) {
|
||
|
|
|
||
|
|
# Check if parent needs compilation (is in pending jobs)
|
||
|
|
if ( exists $pending_jobs{$parent} ) {
|
||
|
|
push @{ $dependencies{$base} }, $parent;
|
||
|
|
push @{ $rdependencies{$parent} }, $base;
|
||
|
|
}
|
||
|
|
|
||
|
|
# If parent doesn't need compilation, mark it as completed
|
||
|
|
# This handles the case where parent layers are up-to-date
|
||
|
|
else {
|
||
|
|
$completed{$parent} = 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Debug output if verbose
|
||
|
|
if ( $ENV{DW_DEBUG_S2} ) {
|
||
|
|
print "Dependency graph:\n";
|
||
|
|
for my $base ( sort keys %dependencies ) {
|
||
|
|
my $deps =
|
||
|
|
@{ $dependencies{$base} }
|
||
|
|
? join( ", ", @{ $dependencies{$base} } )
|
||
|
|
: "none";
|
||
|
|
print " $base depends on: $deps\n";
|
||
|
|
}
|
||
|
|
print "Pre-completed layers: " . join( ", ", sort keys %completed ) . "\n"
|
||
|
|
if %completed;
|
||
|
|
}
|
||
|
|
|
||
|
|
my @running_pids; # Array of [pid, base] pairs
|
||
|
|
my $max_parallel = $opt_parallel || 8; # Use command line option or default
|
||
|
|
|
||
|
|
# Special case: if parallel=1, use original serial behavior
|
||
|
|
if ( $max_parallel == 1 ) {
|
||
|
|
print "Using serial compilation (parallel=1)\n";
|
||
|
|
for my $job (@layers_to_compile) {
|
||
|
|
my $pid = $compile_layer->($job);
|
||
|
|
waitpid( $pid, 0 );
|
||
|
|
die "S2 compilation failed" if $? >> 8 != 0;
|
||
|
|
}
|
||
|
|
$dbh = LJ::get_db_writer();
|
||
|
|
print "All layer compilations completed successfully!\n";
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
|
||
|
|
print "Using maximum $max_parallel parallel compilation processes\n";
|
||
|
|
|
||
|
|
while ( %pending_jobs || @running_pids ) {
|
||
|
|
|
||
|
|
# Wait for any completed jobs first if we have running processes
|
||
|
|
if (@running_pids) {
|
||
|
|
my $finished_pid = waitpid( -1, 0 ); # Wait for any child
|
||
|
|
my $exit_status = $? >> 8;
|
||
|
|
|
||
|
|
if ( $exit_status != 0 ) {
|
||
|
|
|
||
|
|
# Kill remaining children and die
|
||
|
|
kill 'TERM', map { $_->[0] } @running_pids;
|
||
|
|
die "S2 compilation failed with exit status $exit_status";
|
||
|
|
}
|
||
|
|
|
||
|
|
# Find which job completed and mark it done
|
||
|
|
for my $i ( 0 .. $#running_pids ) {
|
||
|
|
if ( $running_pids[$i][0] == $finished_pid ) {
|
||
|
|
my $completed_base = $running_pids[$i][1];
|
||
|
|
splice @running_pids, $i, 1;
|
||
|
|
$completed{$completed_base} = 1;
|
||
|
|
print "Completed compilation of $completed_base\n";
|
||
|
|
if ( $ENV{DW_DEBUG_S2} ) {
|
||
|
|
print "Completed layers now: "
|
||
|
|
. join( ", ", sort keys %completed ) . "\n";
|
||
|
|
print "Remaining pending jobs: "
|
||
|
|
. scalar( keys %pending_jobs ) . "\n";
|
||
|
|
}
|
||
|
|
last;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Start new jobs if we have capacity and ready jobs
|
||
|
|
while ( @running_pids < $max_parallel && %pending_jobs ) {
|
||
|
|
|
||
|
|
# Find a job with all dependencies completed
|
||
|
|
my $ready_job;
|
||
|
|
my @ready_candidates;
|
||
|
|
for my $base ( keys %pending_jobs ) {
|
||
|
|
my $all_deps_done = 1;
|
||
|
|
my @unsatisfied_deps;
|
||
|
|
for my $dep ( @{ $dependencies{$base} } ) {
|
||
|
|
if ( !$completed{$dep} ) {
|
||
|
|
$all_deps_done = 0;
|
||
|
|
push @unsatisfied_deps, $dep;
|
||
|
|
if ( $ENV{DW_DEBUG_S2}
|
||
|
|
&& $dep eq 'core2'
|
||
|
|
&& $base eq 'core2base/layout' )
|
||
|
|
{
|
||
|
|
print "DEBUG: $base depends on $dep, completed{$dep} = "
|
||
|
|
. ( $completed{$dep} // 'undef' ) . "\n";
|
||
|
|
print "DEBUG: Available completed keys: "
|
||
|
|
. join( ", ", sort keys %completed ) . "\n";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if ($all_deps_done) {
|
||
|
|
push @ready_candidates, $base;
|
||
|
|
}
|
||
|
|
elsif ( $ENV{DW_DEBUG_S2} && @unsatisfied_deps <= 2 ) {
|
||
|
|
|
||
|
|
# Debug: show a few jobs that are almost ready
|
||
|
|
print "Almost ready - $base waiting for: "
|
||
|
|
. join( ", ", @unsatisfied_deps ) . "\n";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (@ready_candidates) {
|
||
|
|
$ready_job = $ready_candidates[0]; # Take the first ready job
|
||
|
|
if ( $ENV{DW_DEBUG_S2} ) {
|
||
|
|
print "Ready jobs this round: "
|
||
|
|
. join( ", ", @ready_candidates ) . "\n";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
last unless $ready_job; # No jobs ready to start
|
||
|
|
|
||
|
|
my $job = delete $pending_jobs{$ready_job};
|
||
|
|
my $pid = $compile_layer->($job);
|
||
|
|
push @running_pids, [ $pid, $ready_job ];
|
||
|
|
if ( $ENV{DW_DEBUG_S2} ) {
|
||
|
|
print "Started compilation of $ready_job (PID $pid)\n";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Safety check to prevent infinite loops
|
||
|
|
if ( !@running_pids && %pending_jobs ) {
|
||
|
|
my @remaining = keys %pending_jobs;
|
||
|
|
print "Compilation deadlock detected. Remaining jobs: "
|
||
|
|
. join( ", ", @remaining ) . "\n";
|
||
|
|
print "Dependency analysis:\n";
|
||
|
|
for my $job (@remaining) {
|
||
|
|
my $deps =
|
||
|
|
@{ $dependencies{$job} }
|
||
|
|
? join( ", ", @{ $dependencies{$job} } )
|
||
|
|
: "none";
|
||
|
|
my $unsatisfied = [];
|
||
|
|
for my $dep ( @{ $dependencies{$job} } ) {
|
||
|
|
push @$unsatisfied, $dep unless $completed{$dep};
|
||
|
|
}
|
||
|
|
my $unsatisfied_str =
|
||
|
|
@$unsatisfied ? join( ", ", @$unsatisfied ) : "none";
|
||
|
|
print " $job: depends on [$deps], unsatisfied: [$unsatisfied_str]\n";
|
||
|
|
}
|
||
|
|
die "Cannot proceed with compilation.";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Reconnect to database after all forks
|
||
|
|
$dbh = LJ::get_db_writer();
|
||
|
|
|
||
|
|
print "All layer compilations completed successfully!\n";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($LJ::IS_DEV_SERVER) {
|
||
|
|
|
||
|
|
# now, delete any system layers that don't below (from previous imports?)
|
||
|
|
my @del_ids;
|
||
|
|
my $sth =
|
||
|
|
$dbh->prepare("SELECT s2lid FROM s2layers WHERE userid=? AND NOT type='user'");
|
||
|
|
$sth->execute($sysid);
|
||
|
|
while ( my $id = $sth->fetchrow_array ) {
|
||
|
|
next if $known_id{$id};
|
||
|
|
push @del_ids, $id;
|
||
|
|
}
|
||
|
|
|
||
|
|
# if we need to delete things, prompt before blowing away system layers
|
||
|
|
if (@del_ids) {
|
||
|
|
print
|
||
|
|
"\nWARNING: The following S2 layer ids are known as system layers but are no longer\n"
|
||
|
|
. "present in the import files. If this is expected and you really want to DELETE\n"
|
||
|
|
. "these layers, type 'YES' (in all capitals).\n\nType YES to delete layers "
|
||
|
|
. join( ', ', @del_ids ) . ": ";
|
||
|
|
my $inp = <STDIN>;
|
||
|
|
if ( $inp =~ /^YES$/ ) {
|
||
|
|
print "\nOkay, I am PERMANENTLY DELETING the layers.\n";
|
||
|
|
LJ::S2::delete_layer($_) foreach @del_ids;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
print "\nOkay, I am NOT deleting the layers.\n";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($has_new_layer) {
|
||
|
|
$LJ::CACHED_PUBLIC_LAYERS = undef;
|
||
|
|
LJ::MemCache::delete("s2publayers");
|
||
|
|
|
||
|
|
print "\nCleared styles cache.\n";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub populate_basedata {
|
||
|
|
|
||
|
|
# base data
|
||
|
|
foreach my $ffile ( LJ::get_all_files( "bin/upgrading/base-data.sql", home_first => 1 ) ) {
|
||
|
|
my $d_file = $ffile;
|
||
|
|
$d_file =~ s!^\Q$LJ::HOME\E/*!!;
|
||
|
|
|
||
|
|
print "Populating database with $d_file.\n";
|
||
|
|
open( BD, $ffile ) or die "Can't open $d_file file\n";
|
||
|
|
while ( my $q = <BD> ) {
|
||
|
|
chomp $q; # remove newline
|
||
|
|
next unless ( $q =~ /^(REPLACE|INSERT|UPDATE)/ );
|
||
|
|
chop $q; # remove semicolon
|
||
|
|
$dbh->do($q);
|
||
|
|
if ( $dbh->err ) {
|
||
|
|
print "$q\n";
|
||
|
|
die "# ERROR: " . $dbh->errstr . "\n";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
close(BD);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub populate_proplists {
|
||
|
|
foreach my $ffile ( LJ::get_all_files( "bin/upgrading/proplists.dat", home_first => 1 ) ) {
|
||
|
|
populate_proplist_file( $ffile, "general" );
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach my $ffile ( LJ::get_all_files( "bin/upgrading/proplists-local.dat", home_first => 1 ) )
|
||
|
|
{
|
||
|
|
populate_proplist_file( $ffile, "local" );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub populate_proplist_file {
|
||
|
|
my ( $file, $scope ) = @_;
|
||
|
|
open( my $fh, $file ) or die "Failed to open $file: $!";
|
||
|
|
|
||
|
|
my %pk = (
|
||
|
|
'userproplist' => 'name',
|
||
|
|
'logproplist' => 'name',
|
||
|
|
'media_prop_list' => 'name',
|
||
|
|
'talkproplist' => 'name',
|
||
|
|
'usermsgproplist' => 'name',
|
||
|
|
);
|
||
|
|
my %id = (
|
||
|
|
'userproplist' => 'upropid',
|
||
|
|
'logproplist' => 'propid',
|
||
|
|
'media_prop_list' => 'propid',
|
||
|
|
'talkproplist' => 'tpropid',
|
||
|
|
'usermsgproplist' => 'propid',
|
||
|
|
);
|
||
|
|
|
||
|
|
my $table; # table
|
||
|
|
my $pk; # table's primary key name
|
||
|
|
my $pkv; # primary key value
|
||
|
|
my %vals; # hash of column -> value, including primary key
|
||
|
|
|
||
|
|
my %current_props;
|
||
|
|
|
||
|
|
foreach $table ( keys %pk ) {
|
||
|
|
$pk = $pk{$table};
|
||
|
|
my $id = $id{$table};
|
||
|
|
|
||
|
|
$current_props{$table} = $dbh->selectall_hashref( "SELECT `$id`,`$pk` FROM `$table`", $pk );
|
||
|
|
}
|
||
|
|
|
||
|
|
my $insert = sub {
|
||
|
|
return unless %vals;
|
||
|
|
my $sets = join( ", ", map { "$_=" . $dbh->quote( $vals{$_} ) } keys %vals );
|
||
|
|
my $idk = $id{$table};
|
||
|
|
|
||
|
|
my $rv = 0;
|
||
|
|
unless ( $current_props{$table}{$pkv} ) {
|
||
|
|
$rv = $dbh->do("INSERT INTO $table SET $sets");
|
||
|
|
die $dbh->errstr if $dbh->err;
|
||
|
|
$current_props{$table}{$pkv} =
|
||
|
|
{ name => $pkv, $idk => $dbh->last_insert_id( undef, undef, $table, $idk ) };
|
||
|
|
}
|
||
|
|
|
||
|
|
# zero-but-true: see if row didn't exist before, so above did nothing.
|
||
|
|
# in that case, update it.
|
||
|
|
if ( $rv < 1 ) {
|
||
|
|
$rv = $dbh->do( "UPDATE $table SET $sets WHERE $pk=?", undef, $pkv );
|
||
|
|
die $dbh->errstr if $dbh->err;
|
||
|
|
}
|
||
|
|
|
||
|
|
$table = undef;
|
||
|
|
%vals = ();
|
||
|
|
};
|
||
|
|
while (<$fh>) {
|
||
|
|
next if /^\#/;
|
||
|
|
|
||
|
|
if (/^(\w+)\.(\w+):/) {
|
||
|
|
$insert->();
|
||
|
|
( $table, $pkv ) = ( $1, $2 );
|
||
|
|
$pk = $pk{$table} or die "Don't know non-numeric primary key for table '$table'";
|
||
|
|
$vals{$pk} = $pkv;
|
||
|
|
$vals{"scope"} = $scope;
|
||
|
|
next;
|
||
|
|
}
|
||
|
|
if (/^\s+(\w+)\s*:\s*(.*)/) {
|
||
|
|
die "Unexpected line: $_ when not in a block" unless $table;
|
||
|
|
$vals{$1} = $2;
|
||
|
|
next;
|
||
|
|
}
|
||
|
|
if (/\S/) {
|
||
|
|
die "Unxpected line: $_";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
$insert->();
|
||
|
|
close($fh);
|
||
|
|
}
|
||
|
|
|
||
|
|
sub populate_external_moods {
|
||
|
|
my $moodfile = "$ENV{'LJHOME'}/bin/upgrading/moods-external.dat";
|
||
|
|
|
||
|
|
if ( open MOODFILE, "<$moodfile" ) {
|
||
|
|
print "Populating mood data for external sites.\n";
|
||
|
|
|
||
|
|
# $siteid => { $mood => { siteid => $siteid, mood => $mood, moodid => $moodid } }
|
||
|
|
my $moods = $dbh->selectall_hashref( "SELECT siteid, mood, moodid FROM external_site_moods",
|
||
|
|
[ 'siteid', 'mood' ] );
|
||
|
|
|
||
|
|
foreach my $line (<MOODFILE>) {
|
||
|
|
chomp $line;
|
||
|
|
|
||
|
|
if ( $line =~ /^(\d+)\s+(\d+)\s+(.+)$/ ) {
|
||
|
|
my ( $siteid, $moodid, $mood ) = ( $1, $2, $3 );
|
||
|
|
|
||
|
|
unless ( $moods->{$siteid}
|
||
|
|
&& $moods->{$siteid}->{$mood}
|
||
|
|
&& $moods->{$siteid}->{$mood}->{moodid} eq $moodid )
|
||
|
|
{
|
||
|
|
$dbh->do(
|
||
|
|
"REPLACE INTO external_site_moods ( siteid, mood, moodid ) VALUES ( ?, ?, ? )",
|
||
|
|
undef, $siteid, $mood, $moodid
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
close MOODFILE;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub populate_moods {
|
||
|
|
foreach my $moodfile ( LJ::get_all_files( "bin/upgrading/moods.dat", home_first => 1 ) ) {
|
||
|
|
if ( open( M, $moodfile ) ) {
|
||
|
|
my $file = $moodfile;
|
||
|
|
$file =~ s!^\Q$LJ::HOME\E/*!!;
|
||
|
|
|
||
|
|
print "Populating mood data [ $file ].\n";
|
||
|
|
|
||
|
|
my %mood; # id -> [ mood, parent_id ]
|
||
|
|
my $sth = $dbh->prepare("SELECT moodid, mood, parentmood, weight FROM moods");
|
||
|
|
$sth->execute;
|
||
|
|
while ( @_ = $sth->fetchrow_array ) { $mood{ $_[0] } = [ $_[1], $_[2], $_[3] ]; }
|
||
|
|
|
||
|
|
my %moodtheme; # name -> [ id, des ]
|
||
|
|
$sth =
|
||
|
|
$dbh->prepare("SELECT moodthemeid, name, des FROM moodthemes WHERE is_public='Y'");
|
||
|
|
$sth->execute;
|
||
|
|
while ( @_ = $sth->fetchrow_array ) { $moodtheme{ $_[1] } = [ $_[0], $_[2] ]; }
|
||
|
|
|
||
|
|
my $themeid; # current themeid (from existing db or just made)
|
||
|
|
my %data; # moodid -> "$url$width$height" (for equality test)
|
||
|
|
|
||
|
|
while (<M>) {
|
||
|
|
chomp;
|
||
|
|
if (/^MOOD\s+(\d+)\s+(.+)\s+(\d+)\s+(\d+)\s*$/) {
|
||
|
|
my ( $id, $mood, $parid, $weight ) = ( $1, $2, $3, $4 );
|
||
|
|
if ( !$mood{$id}
|
||
|
|
|| $mood{$id}->[0] ne $mood
|
||
|
|
|| $mood{$id}->[1] ne $parid )
|
||
|
|
{
|
||
|
|
$dbh->do(
|
||
|
|
"REPLACE INTO moods (moodid, mood, parentmood, weight) VALUES (?,?,?,?)",
|
||
|
|
undef, $id, $mood, $parid, $weight
|
||
|
|
);
|
||
|
|
}
|
||
|
|
elsif ( !defined $mood{$id}->[2] ) {
|
||
|
|
$dbh->do( "UPDATE moods SET weight = ? WHERE moodid = ?",
|
||
|
|
undef, $weight, $id );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (/^MOODTHEME\s+(.+?)\s*:\s*(.+)$/) {
|
||
|
|
my ( $name, $des ) = ( $1, $2 );
|
||
|
|
%data = ();
|
||
|
|
if ( $moodtheme{$name} ) {
|
||
|
|
$themeid = $moodtheme{$name}->[0];
|
||
|
|
if ( $moodtheme{$name}->[1] ne $des ) {
|
||
|
|
$dbh->do( "UPDATE moodthemes SET des=? WHERE moodthemeid=?",
|
||
|
|
undef, $des, $themeid );
|
||
|
|
}
|
||
|
|
$sth = $dbh->prepare( "SELECT moodid, picurl, width, height "
|
||
|
|
. "FROM moodthemedata WHERE moodthemeid=?" );
|
||
|
|
$sth->execute($themeid);
|
||
|
|
while ( @_ = $sth->fetchrow_array ) {
|
||
|
|
$data{ $_[0] } = "$_[1]$_[2]$_[3]";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$dbh->do(
|
||
|
|
"INSERT INTO moodthemes (ownerid, name, des, is_public) "
|
||
|
|
. "VALUES (?,?,?,'Y')",
|
||
|
|
undef, $su->{'userid'}, $name, $des
|
||
|
|
);
|
||
|
|
$themeid = $dbh->{'mysql_insertid'};
|
||
|
|
die "Couldn't generate themeid for theme $name\n" unless $themeid;
|
||
|
|
}
|
||
|
|
next;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (/^(\d+)\s+(\S+)\s+(\d+)\s+(\d+)\s*$/) {
|
||
|
|
next unless $themeid;
|
||
|
|
my ( $moodid, $url, $w, $h ) = ( $1, $2, $3, $4 );
|
||
|
|
next if $data{$moodid} eq "$url$w$h";
|
||
|
|
$dbh->do(
|
||
|
|
"REPLACE INTO moodthemedata (moodthemeid, moodid, picurl, width, height) "
|
||
|
|
. "VALUES (?,?,?,?,?)",
|
||
|
|
undef, $themeid, $moodid, $url, $w, $h
|
||
|
|
);
|
||
|
|
LJ::MemCache::delete( [ $themeid, "moodthemedata:$themeid" ] );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
close M;
|
||
|
|
LJ::MemCache::delete("moods_public");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub skip_opt {
|
||
|
|
return $opt_skip;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub do_sql {
|
||
|
|
my $sql = shift;
|
||
|
|
chomp $sql;
|
||
|
|
my $disp_sql = $sql;
|
||
|
|
$disp_sql =~ s/\bIN \(.+\)/IN (...)/g;
|
||
|
|
print "$disp_sql;\n";
|
||
|
|
if ($opt_sql) {
|
||
|
|
print "# Running...\n";
|
||
|
|
$dbh->do($sql);
|
||
|
|
if ( $dbh->err ) {
|
||
|
|
die "# ERROR: " . $dbh->errstr . "\n";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub do_code {
|
||
|
|
my ( $what, $code ) = @_;
|
||
|
|
print "Code block: $what\n";
|
||
|
|
if ($opt_sql) {
|
||
|
|
print "# Running...\n";
|
||
|
|
$code->();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub try_sql {
|
||
|
|
my $sql = shift;
|
||
|
|
print "$sql;\n";
|
||
|
|
if ($opt_sql) {
|
||
|
|
print "# Non-critical SQL (upgrading only... it might fail)...\n";
|
||
|
|
$dbh->do($sql);
|
||
|
|
if ( $dbh->err ) {
|
||
|
|
print "# Acceptable failure: " . $dbh->errstr . "\n";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub try_alter {
|
||
|
|
my ( $table, $sql ) = @_;
|
||
|
|
return if $cluster && !defined $clustered_table{$table};
|
||
|
|
|
||
|
|
try_sql($sql);
|
||
|
|
|
||
|
|
# columns will have changed, so clear cache:
|
||
|
|
clear_table_info($table);
|
||
|
|
}
|
||
|
|
|
||
|
|
sub do_alter {
|
||
|
|
my ( $table, $sql ) = @_;
|
||
|
|
return if $cluster && !defined $clustered_table{$table};
|
||
|
|
|
||
|
|
do_sql($sql);
|
||
|
|
|
||
|
|
# columns will have changed, so clear cache:
|
||
|
|
clear_table_info($table);
|
||
|
|
}
|
||
|
|
|
||
|
|
sub create_table {
|
||
|
|
my $table = shift;
|
||
|
|
return if $cluster && !defined $clustered_table{$table};
|
||
|
|
|
||
|
|
my $create_sql = $table_create{$table};
|
||
|
|
if ( $opt_innodb && $create_sql !~ /engine=myisam/i ) {
|
||
|
|
$create_sql .= " ENGINE=INNODB";
|
||
|
|
}
|
||
|
|
do_sql($create_sql);
|
||
|
|
|
||
|
|
foreach my $pc ( @{ $post_create{$table} } ) {
|
||
|
|
my @args = @{$pc};
|
||
|
|
my $ac = shift @args;
|
||
|
|
if ( $ac eq "sql" ) {
|
||
|
|
print "# post-create SQL\n";
|
||
|
|
do_sql( $args[0] );
|
||
|
|
}
|
||
|
|
elsif ( $ac eq "sqltry" ) {
|
||
|
|
print "# post-create SQL (necessary if upgrading only)\n";
|
||
|
|
try_sql( $args[0] );
|
||
|
|
}
|
||
|
|
elsif ( $ac eq "code" ) {
|
||
|
|
print "# post-create code\n";
|
||
|
|
$args[0]->( $dbh, $opt_sql );
|
||
|
|
}
|
||
|
|
else { print "# don't know how to do \$ac = $ac"; }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub drop_table {
|
||
|
|
my $table = shift;
|
||
|
|
|
||
|
|
if ($opt_drop) {
|
||
|
|
do_sql("DROP TABLE $table");
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
print "# Not dropping table $table to be paranoid (use --drop)\n";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub mark_clustered {
|
||
|
|
foreach (@_) {
|
||
|
|
$clustered_table{$_} = 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub register_tablecreate {
|
||
|
|
my ( $table, $create ) = @_;
|
||
|
|
|
||
|
|
# we now know of it
|
||
|
|
delete $table_unknown{$table};
|
||
|
|
|
||
|
|
return if $cluster && !defined $clustered_table{$table};
|
||
|
|
|
||
|
|
$table_create{$table} = $create;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub register_tabledrop {
|
||
|
|
my ($table) = @_;
|
||
|
|
$table_drop{$table} = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub post_create {
|
||
|
|
my $table = shift;
|
||
|
|
while ( my ( $type, $what ) = splice( @_, 0, 2 ) ) {
|
||
|
|
push @{ $post_create{$table} }, [ $type, $what ];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub register_alter {
|
||
|
|
my $sub = shift;
|
||
|
|
push @alters, $sub;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub clear_table_info {
|
||
|
|
my $table = shift;
|
||
|
|
delete $coltype{$table};
|
||
|
|
delete $indexname{$table};
|
||
|
|
delete $table_status{$table};
|
||
|
|
}
|
||
|
|
|
||
|
|
sub load_table_info {
|
||
|
|
my $table = shift;
|
||
|
|
|
||
|
|
clear_table_info($table);
|
||
|
|
|
||
|
|
my $sth = $dbh->prepare("DESCRIBE $table");
|
||
|
|
$sth->execute;
|
||
|
|
while ( my $row = $sth->fetchrow_hashref ) {
|
||
|
|
my $type = $row->{'Type'};
|
||
|
|
$type .= " $1" if $row->{'Extra'} =~ /(auto_increment)/i;
|
||
|
|
$coltype{$table}->{ $row->{'Field'} } = lc($type);
|
||
|
|
$coldefault{$table}->{ $row->{'Field'} } = $row->{'Default'};
|
||
|
|
}
|
||
|
|
|
||
|
|
# current physical table properties
|
||
|
|
$table_status{$table} = $dbh->selectrow_hashref("SHOW TABLE STATUS LIKE '$table'");
|
||
|
|
|
||
|
|
$sth = $dbh->prepare("SHOW INDEX FROM $table");
|
||
|
|
$sth->execute;
|
||
|
|
my %idx_type; # name -> "UNIQUE"|"INDEX"
|
||
|
|
my %idx_parts; # name -> []
|
||
|
|
while ( my $ir = $sth->fetchrow_hashref ) {
|
||
|
|
$idx_type{ $ir->{'Key_name'} } = $ir->{'Non_unique'} ? "INDEX" : "UNIQUE";
|
||
|
|
push @{ $idx_parts{ $ir->{'Key_name'} } }, $ir->{'Column_name'};
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach my $idx ( keys %idx_type ) {
|
||
|
|
my $val = "$idx_type{$idx}:" . join( "-", @{ $idx_parts{$idx} } );
|
||
|
|
$indexname{$table}->{$val} = $idx;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub index_name {
|
||
|
|
my ( $table, $idx ) = @_; # idx form is: INDEX:col1-col2-col3
|
||
|
|
load_table_info($table) unless $indexname{$table};
|
||
|
|
return $indexname{$table}->{$idx} || "";
|
||
|
|
}
|
||
|
|
|
||
|
|
sub table_relevant {
|
||
|
|
my $table = shift;
|
||
|
|
return 1 unless $cluster;
|
||
|
|
return 1 if $clustered_table{$table};
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub column_type {
|
||
|
|
my ( $table, $col ) = @_;
|
||
|
|
load_table_info($table) unless $coltype{$table};
|
||
|
|
my $type = $coltype{$table}->{$col};
|
||
|
|
$type ||= "";
|
||
|
|
return $type;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub column_default {
|
||
|
|
my ( $table, $col ) = @_;
|
||
|
|
load_table_info($table) unless exists $coldefault{$table};
|
||
|
|
return $coldefault{$table}->{$col};
|
||
|
|
}
|
||
|
|
|
||
|
|
sub table_status {
|
||
|
|
my ( $table, $col ) = @_;
|
||
|
|
load_table_info($table) unless $table_status{$table};
|
||
|
|
|
||
|
|
return $table_status{$table}->{$col} || "";
|
||
|
|
}
|
||
|
|
|
||
|
|
sub ensure_confirm {
|
||
|
|
my $area = shift;
|
||
|
|
|
||
|
|
return 1 if (
|
||
|
|
$opt_sql
|
||
|
|
&& ( $opt_confirm eq "all"
|
||
|
|
or $opt_confirm eq $area )
|
||
|
|
);
|
||
|
|
|
||
|
|
print STDERR "To proceed with the necessary changes, rerun with -r --confirm=$area\n";
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub set_dbnote {
|
||
|
|
my ( $key, $value ) = @_;
|
||
|
|
return unless $opt_sql && $key && $value;
|
||
|
|
|
||
|
|
return $dbh->do( "REPLACE INTO dbnotes (dbnote, value) VALUES (?,?)", undef, $key, $value );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub check_dbnote {
|
||
|
|
my $key = shift;
|
||
|
|
|
||
|
|
return $dbh->selectrow_array( "SELECT value FROM dbnotes WHERE dbnote=?", undef, $key );
|
||
|
|
}
|