X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~yarrgweb/git?p=ypp-sc-tools.web-live.git;a=blobdiff_plain;f=yarrg%2Fdb-idempotent-populate;h=bb0448ce478389c1c030220514c1cdabe648f588;hp=2cce5c73ccfa571a84e597a6a1406d2067774d72;hb=f62749dec2d8b67a9b4b4a910e1159907654d426;hpb=735a20880b0e6c0b5ad2d62f77f2c3afd0d77ad4 diff --git a/yarrg/db-idempotent-populate b/yarrg/db-idempotent-populate index 2cce5c7..bb0448c 100755 --- a/yarrg/db-idempotent-populate +++ b/yarrg/db-idempotent-populate @@ -36,15 +36,17 @@ use DBI; use Commods; use CommodsDatabase; -my $trace; -if (@ARGV and $ARGV[0] eq '-D') { - $trace=1; +my $trace=0; +while (@ARGV and $ARGV[0] eq '-D') { + $trace++; shift @ARGV; } @ARGV==1 or die; my ($oceanname) = @ARGV; +$|=1; + #---------- setup ---------- parse_info_serverside(); @@ -55,134 +57,70 @@ db_connect(); $dbh->trace(1) if $trace; -#---------- referential integrity constraints ---------- - -# SQLite doesn't support foreign key constraints so we do it by steam: -sub nooutput ($) { - my ($stmts) = @_; - my $ekindcount= 0; - my $letxt= ''; - foreach my $stmt (split /\;/, $stmts) { - next unless $stmt =~ /\S/; - - my $etxt= ''; - $stmt =~ s/^([ \t]*\#.*)$/ $etxt .= $1."\n"; ''; /mge; - $etxt= $letxt unless length $etxt; - $letxt= $etxt; - - $stmt =~ s/^\s+//; $stmt =~ s/\s+$//; - my $sth= $dbh->prepare($stmt); - $sth->execute(); - my $row; - my $ecount= 0; - my @cols= @{ $sth->{NAME_lc} }; - my $w= 11; - while ($row= $sth->fetchrow_hashref) { - if (!$ecount++) { - print STDERR "REFERENTIAL INTEGRITY ERROR\n"; - print STDERR "\n$etxt\n $stmt\n\n"; - printf STDERR "|%-${w}s", $_ foreach @cols; print STDERR "|\n"; - print STDERR "+",('-'x$w) foreach @cols; print STDERR "+\n"; - } - if ($ecount>5) { print STDERR "...\n"; last; } - printf STDERR "|%-$w.${w}s", - (defined $row->{$_} ? $row->{$_} : 'NULL') - foreach @cols; - print STDERR "\n"; - } - next unless $ecount; - - $ekindcount++; - print STDERR "\n\n"; - } - die "REFERENTIAL INTEGRITY ERRORS $ekindcount\n" - if $ekindcount; -} +#---------- schema update code ---------- -sub check_referential_integrity () { - foreach my $bs (qw(buy sell)) { - nooutput(< 1; + #----- ensure table exists ----- + db_doall(<commit(); -} + my @need_recreate; -#---------- schema ---------- + #----- check whether we need to remove autoinc ----- -foreach my $bs (qw(buy sell)) { - db_doall(<prepare(<execute($table,$table); + my ($sql)= $autoinc->fetchrow_array(); + die unless defined $sql; + push @need_recreate, 'remove autoinc' + if $sql =~ m/\bautoinc/i; + } -sub table ($$) { - my ($table,$fields) = @_; - db_doall(" CREATE TABLE IF NOT EXISTS $table (\n$fields );"); + #----- check whether we need to add fields ----- my $check= $dbh->prepare("SELECT * FROM $table LIMIT 1"); $check->execute(); @@ -190,183 +128,306 @@ sub table ($$) { $have_fields{$_}=1 foreach @{ $check->{NAME_lc} }; $check->finish(); - my (@have_fields, @missing_fields); - my $have_field_specs=''; + my @have_fields; + my @aside_fields; + my @have_field_specs; + my @aside_field_specs; - foreach my $fspec (split /,/, $fields) { - next unless $fspec =~ m/\S/; - $fspec =~ m/^\s*(\w+)\s+(\w.*\S)\s*$/ or die "$table $fspec ?"; - my ($f,$spec) = ($1,$2); + foreach my $f (@want_fields) { if ($have_fields{$f}) { push @have_fields, $f; - $have_field_specs .= ",\n" if length $have_field_specs; - $have_field_specs .= "\t$f\t\t$spec\n"; + push @have_field_specs, $want_field_specs{$f}; } else { - push @missing_fields, $f; + my $aside= $want_field_specs{$f}; + $aside =~ s/\bUNIQUE\b//i; + $aside =~ s/\bNOT\s*NULL\b//i; + $aside =~ s/\bPRIMARY\s*KEY\b//i; + $aside =~ s/\s+$//; + push @aside_fields, $f; + push @aside_field_specs, $aside; + push @need_recreate, "field $f"; } } - return unless @missing_fields; - print " Adding missing fields to $table: @missing_fields ...\n"; + #----- Do we need to recreate ? ----- + if (!@need_recreate) { + $table{$table}= $table; + return; + } + #----- Yes, recreate: ----- + + print " Recreating $table: ", join('; ',@need_recreate); + $table{$table}= "aside_$table"; my $have_fields= join ',', @have_fields; + my $aside_fields= join ',', @have_fields, @aside_fields; + my $have_field_specs= join ",\n", @have_field_specs; + my $aside_field_specs= join ",\n", @have_field_specs, @aside_field_specs; db_doall(< $table, + Sql => < $table, + Id => $cpact_idfield, + Updates => $cpact_needupdates, + Fields => [ @want_fields ], + FieldSpecs => $want_field_specs + }; } -table('commods', < commodsortkey($b); + } keys %commods; } +our %posincl; + { - my $insert= $dbh->prepare(<<'END') - INSERT OR IGNORE INTO commods - (unitmass, - unitvolume, - commodname) - VALUES (?,?,?); -END - ; - my $setsizes= $dbh->prepare(<<'END') - UPDATE commods - SET unitmass = ?, - unitvolume = ? - WHERE commodname = ? -END - ; - my $setordval= $dbh->prepare(<<'END') - UPDATE commods - SET ordval = ? - WHERE commodname = ? + my %classorderedcount; + + foreach my $cl (keys %commodclasses) { + $classorderedcount{$cl}= 0; + } + foreach my $commod (commods_ordered()) { + my $cl= $commods{$commod}{Class}; + die "no class for commodity $commod" unless defined $cl; + + my $clid= $commodclasses{$cl}; + die "unknown class $cl for $commod ".(join '|', sort keys %commodclasses) unless defined $clid; + + if (defined $commods{$commod}{Ordval}) { + $posincl{$commod}= ++$classorderedcount{$cl}; + } else { + $posincl{$commod}= 0; + } + } + + db_doall(<prepare(<<'END') - UPDATE commods - SET commodclass = ? - WHERE commodname = ? + my $addclass= $dbh->prepare(<prepare(<<'END') - UPDATE commods - SET inclass = ? + foreach my $cl (sort keys %commodclasses) { + my $clname= $cl; + $clname =~ s/_/ /g; + $addclass->execute($commodclasses{$cl}+1, + ucfirst $clname, + $classorderedcount{$cl}); + } +} + +{ + my @valuefields= qw( + unitmass + unitvolume + commodclassid + ordval + posinclass + ); + my $insert= $dbh->prepare(" + INSERT OR IGNORE INTO $table{commods} + ( commodname, + ".join(", + ", @valuefields)." ) + VALUES (?,".join(',', map {'?'} @valuefields).") +"); + my $update= $dbh->prepare(" + UPDATE $table{commods} + SET ".join(", + ", map { "$_ = ?" } @valuefields)." WHERE commodname = ? -END - ; - my %incl; - foreach my $commod (sort { - commodsortkey($a) cmp commodsortkey($b) - } keys %commods) { +"); + foreach my $commod (commods_ordered()) { my $c= $commods{$commod}; die "no mass for $commod" unless defined $c->{Mass}; die "no volume for $commod" unless defined $c->{Volume}; - my @qa= ($c->{Mass}, $c->{Volume}, $commod); - $insert->execute(@qa); - $setsizes->execute(@qa); - $setordval->execute($c->{Ordval} || 0, $commod); my $cl= $c->{Class}; - $setclass->execute($cl, $commod); + my $clid= $commodclasses{$cl}+1; + + my @valuevalues= ( + $c->{Mass}, + $c->{Volume}, + $clid, + commodsortkey($commod), + $posincl{$commod} + ); + $insert->execute($commod, @valuevalues); + $update->execute(@valuevalues, $commod); + } - if (defined $c->{Ordval} and defined $cl) { - $incl{$cl}++; - $setinclass->execute($incl{$cl}, $commod); - } + my $search= $dbh->prepare(<prepare(<prepare(<prepare(<<'END') - INSERT INTO commodclasses - (commodclass, size) - VALUES (?,?) + $search->execute(); + my $any=0; + while (my $row= $search->fetchrow_hashref()) { + next if defined $commods{$row->{'commodname'}}; + print $any++ ? '; ' : " Dropping old commodities: ", + $row->{'commodname'}; + foreach my $bs (qw(buy sell)) { + $check{$bs}->execute($row->{'commodid'}); + my $problem= $check{$bs}->fetchrow_hashref(); + if ($problem) { + print "\n"; + die <{'commodid'} + $row->{'commodname'} + but + $bs + $problem->{'islandname'} + $problem->{'stallname'} + $problem->{'qty'} at $problem->{'price'} END - ; - foreach my $cl (sort keys %incl) { - $addclass->execute($cl, $incl{$cl}); + } + } + $delete->execute($row->{'commodid'}); } - chkcommit(); + print ".\n" if $any; } #---------- vessel types ---------- { - my $idempotent= $dbh->prepare(<<'END') - INSERT OR REPLACE INTO vessels (name, shot, mass, volume) - VALUES (?,?,?,?) + my $idempotent= $dbh->prepare(<{$_} } qw(Mass Volume)); $idempotent->execute(@qa); } - chkcommit(); +} + +#---------- transfer data back from any recreated tables ---------- + +foreach my $tb (@need_transfer_back) { + my $tab= $tb->{Table}; + print " Retransferring $tab..."; + + if (!eval { + db_doall($tb->{Sql}); + 1; + }) { + my $emsg= $@; + my $w=20; + print STDERR "\n=== $tab retransfer failed, dumping:\n"; + my $dumph= $dbh->prepare("SELECT * FROM aside_$tab"); + $dumph->execute(); + my @cols= @{ $dumph->{NAME_lc} }; + dumptab_head(\*STDERR,$w,\@cols); + my $row; + while ($row= $dumph->fetchrow_hashref()) { + dumptab_row_hashref(\*STDERR,$w,\@cols,$row); + } + die "\n$emsg"; + } + print "\n"; + $table{$tab}= $tab; +} + +#---------- create indices ---------- + +foreach my $bs (qw(buy sell)) { + db_doall(<prepare("SELECT $minmax($f) FROM $tab"); + $sth->execute(); + my ($val)= $sth->fetchrow_array(); + return defined($val) ? $val : '?'; +} + +foreach my $cp (@need_compact) { + print " Compacting $cp->{Table}"; + my $tab= $cp->{Table}; + my $id= $cp->{Id}; + my $tmp_field_specs= $cp->{FieldSpecs}; + my $fields= join ',', @{$cp->{Fields}}; + $tmp_field_specs =~ s/\bprimary key\b/UNIQUE/i or + die "$tab $tmp_field_specs ?"; + db_doall(<1..%d:", + $cp->{Id}, + getminmax($tab,'min',$id), + $oldmax, + getminmax("idlookup_$tab",'max',"new_$id")); + my @updates= @{ $cp->{Updates} }; + while (@updates) { + my $utabs= shift @updates; + my $ufields= shift @updates; + foreach my $utab (@$utabs) { + printf(" %s",$utab); + my $fh= '.'; + foreach my $ufield (@$ufields) { + printf("%s%s",$fh,$ufield); $fh=','; + db_doall(<{AutoCommit} = 1; + $dbh->do('VACUUM'); }