/[rserv]/misc/rserv_init.pl
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Contents of /misc/rserv_init.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.3 - (show annotations)
Sat Aug 16 09:41:48 2003 UTC (20 years, 8 months ago) by dpavlin
Branch: MAIN
CVS Tags: before_onlytables, before_multmaster, r_0_3, HEAD
Changes since 1.2: +6 -6 lines
File MIME type: text/plain
use new command line options

1 #!/usr/bin/perl -w
2 ##############################################################################
3 #
4 # Script: rserv_init.pl
5 #
6 # Author: Grant McLean <grant@catalyst.net.nz>
7 #
8 # Description:
9 #
10 # Initialises the database tables and triggers required on a replication
11 # master. Run this script with no arguments for a usage message.
12 #
13 # Note this script uses 'Pg', the native Postgres Perl API rather than DBD::Pg
14 # for compatibility with the standard RServ scripts.
15 #
16
17 use strict;
18 use Getopt::Std;
19 use Pod::Usage;
20
21 use Pg;
22
23 my $basedir;
24 BEGIN {
25 $basedir = $0; $basedir =~ s#/[^/]+$##;
26 unshift(@INC, "$basedir/../share");
27 }
28 #use lib '/usr/lib/postgresql/share/contrib/';
29
30 use RServ;
31
32
33 ##############################################################################
34 # G L O B A L V A R I A B L E S
35 ##############################################################################
36
37 our $master_sql = "$basedir/master.sql";
38 our $slave_sql = "$basedir/slave.sql";
39 our $db_changed = 0;
40
41
42 ##############################################################################
43 # Parse command line arguments and handle errors
44 #
45
46 our %opt;
47 getopts('mst:h:u:p:o?', \%opt) or pod2usage(-exitstatus => 1, -verbose => 0);
48
49 pod2usage(-exitstatus => 0, -verbose => 2) if($opt{'?'});
50
51 unless(@ARGV) {
52 pod2usage(
53 -exitstatus => 1, -verbose => 0,
54 -message => 'You must specify the database name'
55 );
56 }
57
58 my $cmd_count = 0;
59 foreach (qw(m s t)) { $cmd_count++ if exists $opt{$_}; }
60 if($cmd_count != 1) {
61 print "$cmd_count\n";
62 pod2usage(
63 -exitstatus => 1, -verbose => 0,
64 -message =>
65 'You must specify one of: -m for MASTER, -s for SLAVE or -t for TRIGGER'
66 );
67 }
68
69 $opt{d} = shift(@ARGV);
70
71
72 ##############################################################################
73 # Connect to database and 'do the business'
74 #
75
76 my $conn = db_connect();
77
78 if(exists $opt{t}) {
79 toggle_app_triggers($conn, $opt{t});
80 exit;
81 }
82
83 check_repl_tables($conn);
84 my %table_map = get_unique_columns($conn);
85 init_repl_tables($conn);
86
87 if($opt{m}) {
88 init_repl_triggers($conn, %table_map);
89 }
90 elsif($opt{s}) {
91 init_slave_tables($conn, %table_map);
92 toggle_app_triggers($conn, 'off');
93 }
94
95 exit(0);
96
97
98
99 ##############################################################################
100 # S U B R O U T I N E S
101 ##############################################################################
102
103 sub abort {
104 my($message) = @_;
105
106 print "$message\nOperation aborted - ";
107 if($db_changed) {
108 print "warning: some updates were applied!\n";
109 }
110 else {
111 print "no action taken\n";
112 }
113 exit(1);
114 }
115
116
117 ##############################################################################
118 # Connect to database
119 #
120
121 sub db_connect {
122
123 my $conn_str = "dbname=$opt{d}";
124
125 $conn_str .= " host=$opt{h}" if(defined($opt{h}));
126 $conn_str .= " user=$opt{u}" if(defined($opt{u}));
127 $conn_str .= " password=$opt{p}" if(defined($opt{p}));
128
129
130 my $conn = Pg::connectdb($conn_str);
131
132 if(!ref($conn) or $conn->status != PGRES_CONNECTION_OK) {
133 abort "Pg::connectdb($conn_str) failed.";
134 }
135
136 return $conn;
137 }
138
139
140 ##############################################################################
141 # Determine whether replication structures have already been set up - bail out
142 # if they have and -o (overwrite) was not specified.
143 #
144
145 sub check_repl_tables {
146 my($conn) = @_;
147
148 my $relname = $opt{m} ? '_rserv_tables_' : '_rserv_slave_tables_';
149
150 my $result = $conn->exec(qq(
151 select count(*) from pg_class where relname = '$relname'
152 ));
153
154 abort $conn->errorMessage if($result->resultStatus ne PGRES_TUPLES_OK);
155
156 my($count) = $result->fetchrow;
157
158 if($count > 0) {
159 print "Replication structures have already been set up in this database";
160 abort "" unless($opt{o});
161 print " - Overwriting\n";
162 }
163
164 }
165
166
167 ##############################################################################
168 # Initialise replication structures.
169 #
170
171 sub init_repl_tables {
172 my($conn) = @_;
173
174 print "Creating replication tables\n";
175
176 my $sql_file = $opt{m} ? $master_sql : $slave_sql;
177
178 local($/) = ';';
179 local(*SQL);
180 open(SQL, '<', $sql_file) or abort "open($sql_file) failed: $!";
181
182 while(<SQL>) {
183 s/\s*--.*$//mg; # Strip comments
184 s/(^\s+|\s+$)//sg; # Strip leading+trainling whitespace
185 next unless /\S/; # Skip empty queries
186
187 my $sql = $_;
188 my $result = $conn->exec($sql);
189 $db_changed = 1;
190
191 if($result->resultStatus ne PGRES_COMMAND_OK) {
192 if($sql !~ /^drop\b/i) {
193 abort $conn->errorMessage . "\n$sql";
194 }
195 }
196 }
197
198 }
199
200
201
202 ##############################################################################
203 # For each table in the database, determine the name of the column that can be
204 # used as a unique identifier. On success, returns a hash of table name to
205 # column name mappings. Aborts on failure (ie: if a table has no unique
206 # column)
207 #
208
209 sub get_unique_columns {
210 my($conn) = @_;
211
212 print "Determining unique column for each table\n";
213
214 my $result = $conn->exec(qq(
215 select relname
216 from pg_class
217 where relkind = 'r'
218 and relname not like 'pg%'
219 and relname not like '_rserv%'
220 order by relname
221 ));
222
223 abort $conn->errorMessage if($result->resultStatus ne PGRES_TUPLES_OK);
224
225 my @table_names = ();
226 my($name);
227 while(($name) = $result->fetchrow) {
228 push @table_names, $name;
229 }
230
231 my %unique_key = ();
232 my @unindexed = ();
233 foreach $name (@table_names) {
234 $unique_key{$name} = find_unique_key($conn, $name);
235 push @unindexed, $name unless($unique_key{$name});
236 }
237
238 if(@unindexed) {
239 my $message =
240 "The following table(s) have no unique column\n " .
241 join("\n ", @unindexed);
242 #abort $message;
243 print $message, "\n";
244 }
245
246 return(%unique_key);
247 }
248
249
250 ##############################################################################
251 # Initialise replication triggers.
252 #
253
254 sub init_repl_triggers {
255 my($conn, %unique_key) = @_;
256
257 print "Creating replication triggers\n";
258
259 my $cmnd = "$basedir/../bin/MasterAddTable";
260 $cmnd .= " --masterhost=$opt{h}" if($opt{h});
261 $cmnd .= " --masteruser=$opt{u}" if($opt{u});
262 $cmnd .= " --masterpassword=$opt{p}" if($opt{p});
263 $cmnd .= " $opt{d}";
264
265 foreach my $name (sort keys %unique_key) {
266 next unless $unique_key{$name}; # debugging only
267 printf " Table: %-28s Column: %s\n", $name, $unique_key{$name} ;
268
269 my $sql = qq(drop trigger _rserv_trigger_t_ on $name);
270 my $result = $conn->exec($sql);
271
272 system "$cmnd $name $unique_key{$name}\n";
273 }
274
275 }
276
277
278 ##############################################################################
279 # Insert rows into _rserv_slave_tables mapping table name to unique column
280 # name.
281 #
282
283 sub init_slave_tables {
284 my($conn, %unique_key) = @_;
285
286 print "Initialising slave tables\n";
287
288 my $cmnd = "$basedir/../bin/SlaveAddTable";
289 $cmnd .= " --slavehost=$opt{h}" if($opt{h});
290 $cmnd .= " --slaveuser=$opt{u}" if($opt{u});
291 $cmnd .= " --slavepassword=$opt{p}" if($opt{p});
292 $cmnd .= " $opt{d}";
293
294 foreach my $name (sort keys %unique_key) {
295 next unless $unique_key{$name}; # debugging only
296 printf " Table: %-28s Column: %s\n", $name, $unique_key{$name} ;
297 system "$cmnd $name $unique_key{$name}\n";
298 }
299
300 }
301
302
303 ##############################################################################
304 # Enable/disable all application triggers - ie: triggers that are not used by
305 # RServ and are not PostgreSQL integrity constraints.
306 #
307
308 sub toggle_app_triggers {
309 my($conn, $flag) = @_;
310
311 my $bool;
312 if(lc($flag) eq 'on') {
313 print "Enabling Application Triggers\n";
314 $bool = 't';
315 }
316 elsif(lc($flag) eq 'off') {
317 print "Disabling Application Triggers\n";
318 $bool = 'f';
319 }
320 else {
321 abort "Triggers can only be toggled to 'on' or 'off'";
322 }
323
324 my $sql = qq(
325 update pg_trigger
326 set tgenabled = '$bool'
327 where not tgisconstraint
328 and not tgname like 'pg%'
329 and not tgname like '_rserv_%'
330 );
331
332 my $result = $conn->exec($sql);
333
334 if($result->resultStatus ne PGRES_COMMAND_OK) {
335 abort $conn->errorMessage . "\n$sql";
336 }
337
338 }
339
340
341 ##############################################################################
342 # For a given table name, returns the name of a column which has a unique
343 # index. Dies if there is no unique index.
344 #
345
346 sub find_unique_key {
347 my($conn, $table) = @_;
348
349 return 'replication_id' if(has_replication_id($conn, $table));
350
351 my $result = $conn->exec(qq(
352 select pgi.indkey
353 from pg_class pgc, pg_index pgi
354 where pgc.oid = pgi.indrelid
355 and pgi.indisunique
356 and pgc.relname = '$table'
357 ));
358
359 abort $conn->errorMessage if($result->resultStatus ne PGRES_TUPLES_OK);
360
361 my $att_num;
362 my($keys, @key);
363 while(($keys) = $result->fetchrow) {
364 @key = split(/\s+/, $keys);
365 next if(@key != 1);
366 next if($key[0] < 1);
367 $att_num = $key[0] unless(defined($att_num));
368 $att_num = $key[0] if($key[0] < $att_num);
369 }
370
371 return unless defined($att_num);
372
373
374 $result = $conn->exec(qq(
375 select pga.attname
376 from pg_class pgc, pg_attribute pga
377 where pgc.oid = pga.attrelid
378 and pgc.relname = '$table'
379 and pgc.relkind = 'r'
380 and attnum = $att_num
381 ));
382
383 abort $conn->errorMessage if($result->resultStatus ne PGRES_TUPLES_OK);
384
385 my($att_name) = $result->fetchrow;
386
387 abort "Error getting column name for $table.$att_num" unless($att_name);
388
389 return $att_name;
390 }
391
392
393 ##############################################################################
394 # For a given table name, returns true if the table has a column called
395 # 'replication_id' or false otherwise.
396 #
397
398 sub has_replication_id {
399 my($conn, $table) = @_;
400
401 my $result = $conn->exec(qq(
402 select count(*)
403 from pg_class pgc, pg_attribute pga
404 where pgc.oid = pga.attrelid
405 and pgc.relname = '$table'
406 and pgc.relkind = 'r'
407 and pga.attname = 'replication_id'
408 ));
409
410 abort $conn->errorMessage if($result->resultStatus ne PGRES_TUPLES_OK);
411
412 my($count) = $result->fetchrow;
413
414 return $count;
415
416 }
417
418
419 __END__
420
421 =head1 NAME
422
423 rserv_init.pl - Initialise structures for replication master or slave
424
425 =head1 SYNOPSIS
426
427 rserv_init.pl [options] (-m | -s | -t on/off) database
428
429 Options:
430
431 -m master mode
432 -s slave mode
433 -t flag turn application triggers 'on' or 'off'
434 -h host host where database should be created
435 -u user Postgres user which should create the database
436 -p password Postgres user's password
437 -o replace existing replication tables if present
438 -? detailed help message
439
440 =head1 DESCRIPTION
441
442 This script is used to prepare a database for replication using RServ.
443
444 It must be invoked with a database name and either '-m' to initialise
445 replication structures for a master or '-s' to initialise replication
446 structures for a slave.
447
448 Initialising a master will create the following tables and will also
449 create a trigger on every table to log updates:
450
451 _rserv_tables_ stores name of unique column for each table
452 _rserv_log_ tracks which rows of each table have been updated
453 _rserv_servers_ details of slave servers (not used?)
454 _rserv_sync_ tracks which updates have been seen by each slave
455
456 Initialising a slave will create the following tables:
457
458 _rserv_slave_tables_ stores name of unique column for each table
459 _rserv_slave_sync_ tracks which updates this slave has seen
460
461 =head1 OPTIONS
462
463 =over 4
464
465 =item B<-m>
466
467 Initialise structures for a replication MASTER.
468
469 =item B<-s>
470
471 Initialise structures for a replication SLAVE.
472
473 =item B<-t 'on' | 'off'>
474
475 Enable or disable application triggers (ie: all triggers that are not used by
476 RServ and are not PostgreSQL integrity constraints).
477
478 =item B<-h hostname>
479
480 Host on which database should be created (default is local host).
481
482 =item B<-u username>
483
484 Postgres user which should be used to create the database (defaults to current
485 user).
486
487 =item B<-p password>
488
489 Postgres user's password.
490
491 =item B<-o>
492
493 ** WARNING ** This is a very dangerous option - do not enable it unless you
494 understand the implications.
495
496 This option causes the existing replication tables to be dropped and replaced
497 with new tables. This is useful in testing but would have the effect of
498 breaking the syncronisation with any slave(s).
499
500 =item B<-?>
501
502 Prints a detailed help message and exits.
503
504 =back
505
506 =cut
507

  ViewVC Help
Powered by ViewVC 1.1.26