/[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.1 - (show annotations)
Fri Aug 15 20:39:52 2003 UTC (20 years, 9 months ago) by dpavlin
Branch: MAIN
File MIME type: text/plain
contribution from Grant McLean - initialises the database tables and
triggers required on a replication master

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

  ViewVC Help
Powered by ViewVC 1.1.26