/[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

Annotation of /misc/rserv_init.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.1 - (hide annotations)
Fri Aug 15 20:39:52 2003 UTC (20 years, 8 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 dpavlin 1.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