chiark / gitweb /
Do referential integrity check on every incoming update
[ypp-sc-tools.db-live.git] / yarrg / db-idempotent-populate
1 #!/usr/bin/perl -w
2 #
3 # Normally run from
4 #  update-master-info
5 #
6 # usage: ./db-idempotent-populate <Oceanname>
7 #  creates or updates OCEAN-Oceanname.db
8 #  from source-info.txt
9
10 # This is part of the YARRG website.  YARRG is a tool and website
11 # for assisting players of Yohoho Puzzle Pirates.
12 #
13 # Copyright (C) 2009 Ian Jackson <ijackson@chiark.greenend.org.uk>
14 #
15 # This program is free software: you can redistribute it and/or modify
16 # it under the terms of the GNU Affero General Public License as
17 # published by the Free Software Foundation, either version 3 of the
18 # License, or (at your option) any later version.
19 #
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23 # GNU Affero General Public License for more details.
24 #
25 # You should have received a copy of the GNU Affero General Public License
26 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
27 #
28 # Yohoho and Puzzle Pirates are probably trademarks of Three Rings and
29 # are used without permission.  This program is not endorsed or
30 # sponsored by Three Rings.
31
32 use strict (qw(vars));
33
34 use DBI;
35
36 use Commods;
37 use CommodsDatabase;
38
39 my $trace;
40 if (@ARGV and $ARGV[0] eq '-D') {
41         $trace=1;
42         shift @ARGV;
43 }
44
45 @ARGV==1 or die;
46 my ($oceanname) = @ARGV;
47
48 #---------- setup ----------
49
50 parse_info_serverside();
51
52 db_setocean($oceanname);
53 db_writer();
54 db_connect();
55
56 $dbh->trace(1) if $trace;
57
58 #---------- schema ----------
59
60 foreach my $bs (qw(buy sell)) {
61     db_doall(<<END)
62  CREATE TABLE IF NOT EXISTS $bs (
63         commodid        INTEGER                 NOT NULL,
64         islandid        INTEGER                 NOT NULL,
65         stallid         INTEGER                 NOT NULL,
66         price           INTEGER                 NOT NULL,
67         qty             INTEGER                 NOT NULL,
68         PRIMARY KEY (commodid, islandid, stallid)
69  );
70  CREATE INDEX IF NOT EXISTS ${bs}_by_island ON $bs (commodid, islandid, price);
71  CREATE INDEX IF NOT EXISTS ${bs}_by_price  ON $bs (commodid, price, islandid);
72 END
73     ;
74 }
75
76 sub table ($$) {
77     my ($table,$fields) = @_;
78     db_doall(" CREATE TABLE IF NOT EXISTS $table (\n$fields );");
79
80     my $check= $dbh->prepare("SELECT * FROM $table LIMIT 1");
81     $check->execute();
82     my %have_fields;
83     $have_fields{$_}=1 foreach @{ $check->{NAME_lc} };
84     $check->finish();
85
86     my (@have_fields, @missing_fields);
87     my $have_field_specs='';
88
89     foreach my $fspec (split /,/, $fields) {
90         next unless $fspec =~ m/\S/;
91         $fspec =~ m/^\s*(\w+)\s+(\w.*\S)\s*$/ or die "$table $fspec ?";
92         my ($f,$spec) = ($1,$2);
93         if ($have_fields{$f}) {
94             push @have_fields, $f;
95             $have_field_specs .= ",\n" if length $have_field_specs;
96             $have_field_specs .= "\t$f\t\t$spec\n";
97         } else {
98             push @missing_fields, $f;
99         }
100     }
101
102     return unless @missing_fields;
103     print "    Adding missing fields to $table: @missing_fields ...\n";
104
105     my $have_fields= join ',', @have_fields;
106
107     db_doall(<<END);
108  CREATE TEMPORARY TABLE aside_$table (
109 $have_field_specs );
110  INSERT INTO aside_$table SELECT $have_fields FROM $table;
111
112  DROP TABLE $table;
113  CREATE TABLE $table (
114 $fields );
115
116  INSERT INTO $table ($have_fields) SELECT $have_fields FROM aside_$table;
117
118  DROP TABLE aside_$table;
119 END
120 }
121
122 table('commods', <<END);
123         commodid        INTEGER PRIMARY KEY     NOT NULL,
124         commodname      TEXT    UNIQUE          NOT NULL,
125         unitmass        INTEGER,
126         unitvolume      INTEGER,
127         ordval          INTEGER,
128         commodclass     TEXT,
129         inclass         INTEGER
130 END
131
132 table('commodclasses', <<END);
133         commodclass     TEXT    PRIMARY KEY     NOT NULL,
134         size            INTEGER
135 END
136
137 db_doall(<<END)
138  CREATE TABLE IF NOT EXISTS islands (
139         islandid        INTEGER PRIMARY KEY     NOT NULL,
140         islandname      TEXT    UNIQUE          NOT NULL,
141         archipelago     TEXT                    NOT NULL
142  );
143  CREATE TABLE IF NOT EXISTS stalls (
144         stallid         INTEGER PRIMARY KEY     NOT NULL,
145         islandid        INTEGER                 NOT NULL,
146         stallname       TEXT                    NOT NULL,
147         UNIQUE (islandid, stallname)
148  );
149  CREATE TABLE IF NOT EXISTS uploads (
150         islandid        INTEGER PRIMARY KEY     NOT NULL,
151         timestamp       INTEGER                 NOT NULL,
152         message         TEXT                    NOT NULL,
153         clientspec      TEXT                    NOT NULL,
154         serverspec      TEXT                    NOT NULL
155  );
156  CREATE TABLE IF NOT EXISTS dists (
157         aiid            INTEGER                 NOT NULL,
158         biid            INTEGER                 NOT NULL,
159         dist            INTEGER                 NOT NULL,
160         PRIMARY KEY (aiid, biid)
161  );
162  CREATE TABLE IF NOT EXISTS routes (
163         aiid            INTEGER                 NOT NULL,
164         biid            INTEGER                 NOT NULL,
165         dist            INTEGER                 NOT NULL,
166         PRIMARY KEY (aiid, biid)
167  );
168  CREATE TABLE IF NOT EXISTS vessels (
169         name            TEXT                    NOT NULL,
170         mass            INTEGER                 NOT NULL,
171         volume          INTEGER                 NOT NULL,
172         shot            INTEGER                 NOT NULL,
173         PRIMARY KEY (name)
174  );
175 END
176     ;
177
178 db_chkcommit();
179
180 #---------- commodity list ----------
181
182 sub commodsortkey ($) {
183     my ($commod) = @_;
184     my $ordval= $commods{$commod}{Ordval};
185     return sprintf "B %20d", $ordval if defined $ordval;
186     return sprintf "A %s", $commod;
187 }
188
189 {
190     my $insert= $dbh->prepare(<<'END')
191  INSERT OR IGNORE INTO commods
192      (unitmass,
193       unitvolume,
194       commodname)
195      VALUES (?,?,?);
196 END
197     ;
198     my $setsizes= $dbh->prepare(<<'END')
199  UPDATE commods
200      SET unitmass = ?,
201          unitvolume = ?
202      WHERE commodname = ?
203 END
204     ;
205     my $setordval= $dbh->prepare(<<'END')
206  UPDATE commods
207      SET ordval = ?
208      WHERE commodname = ?
209 END
210     ;
211     my $setclass= $dbh->prepare(<<'END')
212  UPDATE commods
213      SET commodclass = ?
214      WHERE commodname = ?
215 END
216     ;
217     my $setinclass= $dbh->prepare(<<'END')
218  UPDATE commods
219      SET inclass = ?
220      WHERE commodname = ?
221 END
222     ;
223     my %incl;
224     foreach my $commod (sort {
225                 commodsortkey($a) cmp commodsortkey($b)
226             } keys %commods) {
227         my $c= $commods{$commod};
228         die "no mass for $commod" unless defined $c->{Mass};
229         die "no volume for $commod" unless defined $c->{Volume};
230         
231         my @qa= ($c->{Mass}, $c->{Volume}, $commod);
232         $insert->execute(@qa);
233         $setsizes->execute(@qa);
234         $setordval->execute($c->{Ordval} || 0, $commod);
235         my $cl= $c->{Class};
236         $setclass->execute($cl, $commod);
237
238         if (defined $c->{Ordval} and defined $cl) {
239             $incl{$cl}++;
240             $setinclass->execute($incl{$cl}, $commod);
241         } elsif (defined $cl) {
242             $incl{$cl} += 0;
243         }
244     }
245     db_doall(<<END);
246  DELETE FROM commodclasses;
247 END
248     my $addclass= $dbh->prepare(<<'END')
249  INSERT INTO commodclasses
250      (commodclass, size)
251      VALUES (?,?)
252 END
253     ;
254     foreach my $cl (sort keys %incl) {
255         $addclass->execute($cl, $incl{$cl});    
256     }
257     db_chkcommit();
258 }
259
260 #---------- vessel types ----------
261 {
262     my $idempotent= $dbh->prepare(<<'END')
263  INSERT OR REPLACE INTO vessels (name, shot, mass, volume)
264                          VALUES (?,?,?,?)
265 END
266     ;
267     foreach my $name (sort keys %vessels) {
268         my $v= $vessels{$name};
269         my $shotdamage= $shotname2damage{$v->{Shot}};
270         die "no shot damage for shot $v->{Shot} for vessel $name"
271             unless defined $shotdamage;
272         my @qa= ($name, $shotdamage, map { $v->{$_} } qw(Mass Volume));
273         $idempotent->execute(@qa);
274     }
275     db_chkcommit();
276 }