/[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.3 - (hide annotations)
Sat Aug 16 09:41:48 2003 UTC (20 years, 9 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 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 dpavlin 1.2 my $basedir;
24     BEGIN {
25     $basedir = $0; $basedir =~ s#/[^/]+$##;
26     unshift(@INC, "$basedir/../share");
27     }
28     #use lib '/usr/lib/postgresql/share/contrib/';
29 dpavlin 1.1
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 dpavlin 1.2 our $master_sql = "$basedir/master.sql";
38     our $slave_sql = "$basedir/slave.sql";
39 dpavlin 1.1 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 dpavlin 1.2 my $cmnd = "$basedir/../bin/MasterAddTable";
260 dpavlin 1.3 $cmnd .= " --masterhost=$opt{h}" if($opt{h});
261     $cmnd .= " --masteruser=$opt{u}" if($opt{u});
262     $cmnd .= " --masterpassword=$opt{p}" if($opt{p});
263 dpavlin 1.1 $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 dpavlin 1.2 my $cmnd = "$basedir/../bin/SlaveAddTable";
289 dpavlin 1.3 $cmnd .= " --slavehost=$opt{h}" if($opt{h});
290     $cmnd .= " --slaveuser=$opt{u}" if($opt{u});
291     $cmnd .= " --slavepassword=$opt{p}" if($opt{p});
292 dpavlin 1.1 $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