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

Diff of /pgdiff

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.1 by dpavlin, Tue Aug 12 10:38:23 2003 UTC revision 1.12 by dpavlin, Wed Oct 29 18:00:39 2003 UTC
# Line 11  use strict; Line 11  use strict;
11  use Getopt::Long;  use Getopt::Long;
12  use DBI;  use DBI;
13  use Data::Dumper;  use Data::Dumper;
14    use Pg::Scheme;
15    
16  $| = 1;  $| = 1;
17    
# Line 42  Options: Line 43  Options:
43          --slavehost=hostname --slaveport=port          --slavehost=hostname --slaveport=port
44          --slaveuser=username --slavepassword=string          --slaveuser=username --slavepassword=string
45          --slavefile=filename          --slavefile=filename
46          --tables[s]=table[,table...]          --table[s]=table[,table...]
47  ";  ";
48  #    exit ((scalar(@ARGV) < 2)? 1:0);  #    exit ((scalar(@ARGV) < 2)? 1:0);
49      exit;      exit;
# Line 62  $sinfo = "$sinfo port=$slaveport" if (de Line 63  $sinfo = "$sinfo port=$slaveport" if (de
63  print "Master connection is $minfo\n" if ($debug);  print "Master connection is $minfo\n" if ($debug);
64  print "Slave connection is $sinfo\n" if ($debug);  print "Slave connection is $sinfo\n" if ($debug);
65    
66  my $mdbh = DBI->connect("DBI:Pg:$minfo", $masteruser, $masterpassword, { PrintError => 1 });  my $mdbh = DBI->connect("DBI:Pg:$minfo", $masteruser, $masterpassword, { PrintError => 0 } );
67  my $sdbh = DBI->connect("DBI:Pg:$sinfo", $slaveuser, $slavepassword, { PrintError => 1 });  if (! $mdbh) {
68            print "Can't connect to master database $master";
69            print "on $masterhost" if ($masterhost);
70            print "\n";
71            exit 1;
72    }
73    my $sdbh = DBI->connect("DBI:Pg:$sinfo", $slaveuser, $slavepassword, { PrintError => 0 });
74    if (! $sdbh) {
75            print "Can't connect to slave database $slave";
76            print "on $slavehost" if ($slavehost);
77            print "\n";
78            exit 1;
79    }
80    
81  my ($diff_shema,$diff_data) = (0,0);  my ($diff_shema,$diff_data) = (0,0);
82    
# Line 74  sub debug_sql { Line 87  sub debug_sql {
87          my $sql = shift;          my $sql = shift;
88          $sql =~ s/[\n\r]/ /gs;          $sql =~ s/[\n\r]/ /gs;
89          $sql =~ s/\s\s+/ /g;          $sql =~ s/\s\s+/ /g;
90          print STDERR "SQL: $sql\n";          print STDERR "DEBUG: SQL: $sql\n";
91  }  }
92    
93  sub debug_row {  sub debug_row {
# Line 82  sub debug_row { Line 95  sub debug_row {
95          my $row = shift;          my $row = shift;
96          my @cols = @_;          my @cols = @_;
97          if (! $row) {          if (! $row) {
98                  print STDERR "ROW data is undef!\n";                  print STDERR "DEBUG: ROW data is undef!\n";
99                  return;                  return;
100          }          }
101          print STDERR "ROW: [",$#cols+1,"] ";          print STDERR "DEBUG: ROW: [",$#cols+1,"] ";
102          foreach my $col (@cols) {          foreach my $col (@cols) {
103                  print STDERR "$col:";                  print STDERR "$col:";
104                  if ($row->{$col}) {                  if ($row->{$col}) {
# Line 98  sub debug_row { Line 111  sub debug_row {
111          print STDERR "\n";          print STDERR "\n";
112  }  }
113    
114    sub debug {
115            return if (!$debug);
116            print STDERR "DEBUG: ",@_;
117    }
118    
119  $verbose = 1 if ($debug);  $verbose = 1 if ($debug);
120    
121    # init object for scheme in master database
122    my $mscheme = new Pg::Scheme( 'dbh' => $mdbh, 'DEBUG' => 0 ) || die "can't query master schema";
123    my $sscheme = new Pg::Scheme( 'dbh' => $sdbh, 'DEBUG' => 0 ) || die "can't query slave schema";
124    
125  # which tables to compare?  # which tables to compare?
126    
127  my @tables;  my @tables = $mscheme->list_tables($tables);
 if ($tables) {  
         @tables = split(/,/,$tables);  
 } else {  
         # take all tables  
         #$sql="select tablename from pg_tables where tablename not like 'pg_%' and tablename not like '_rserv_%'";  
         # show tables (based on psql \dt)  
         $sql = "  
         SELECT c.relname as table  
         FROM pg_catalog.pg_class c  
                 LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace  
         WHERE c.relkind = 'r'  
                 AND n.nspname NOT IN ('pg_catalog', 'pg_toast')  
                 AND pg_catalog.pg_table_is_visible(c.oid)  
                 and c.relname not like '_rserv_%'  
         ";  
         my $sth = $mdbh->prepare($sql);  
         $sth->execute() || die;  
         while(my $row = $sth->fetchrow_hashref()) {  
                 push @tables,$row->{table};  
         }  
 }  
128    
129  print "Comparing tables: ",join(", ",@tables),"\n" if ($debug);  debug "Comparing tables: ".join(", ",@tables)."\n";
130    
131    # start transaction
132    print "begin work;\n";
133    
134    # disable active triggers on slave database
135    my @triggers = $sscheme->get_activetriggers();
136    
137    foreach my $tr (@triggers) {
138            print "update pg_trigger set tgenabled = false where tgname='$tr';\n";
139    }
140    
141  my $cols;  my $cols;
142  my $diff_total = 0;  my $diff_total = 0;
143    
144  foreach my $table (@tables) {  foreach my $table (@tables) {
145    
146          my ($sth);          my $sth;
147    
148          # find table oid  print "-- schema...\n";
149          $sql = "          # diff schema
150          SELECT c.oid, n.nspname, c.relname          foreach my $row (@{$mscheme->pg_attribute($table)}) {
151          FROM pg_catalog.pg_class c  #               print Dumper($row);
         LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace  
         WHERE pg_catalog.pg_table_is_visible(c.oid)  
         AND c.relname = '$table'  
         ORDER BY 2, 3  
         ";  
   
         $sth = $mdbh->prepare($sql);  
         $sth->execute() || die;  
         my $row = $sth->fetchrow_hashref();  
         if (! $row) {  
                 print STDERR "Can't find OID of table '$table'\n";  
                 exit 1;  
152          }          }
153    
154          my $oid = $row->{oid};  print "-- constraints...\n";
155            # diff constraints
156            foreach my $tr (@{$mscheme->triggers($table)}) {
157    #               print Dumper($tr);
158            }
159    print "-- triggers...\n";
160            # diff triggers
161            foreach my $tr (@{$mscheme->triggers($table)}) {
162    #               print Dumper($tr);
163            }
164    
165          # diff schema          # all colums (for insert)
166            my @cols = @{$mscheme->cols($table)};
167    
168          my @cols_notnull;# colums compared by a=b          # colums compared by a=b
169          my @cols_null;  # colums compared by a=b or a is null and b is null          my @cols_notnull = @{$mscheme->cols_notnull($table)};
         my @cols_skip;  # skipped columns  
         my @cols_test;  # all colums to test (without skipped)  
         my @cols;       # all colums (for insert)  
170    
171          my $sql="          # colums compared by a=b or a is null and b is null
172  SELECT a.attname,          my @cols_null = @{$mscheme->cols_null($table)};
 pg_catalog.format_type(a.atttypid, a.atttypmod),  
 a.attnotnull, a.atthasdef, a.attnum  
 FROM pg_catalog.pg_attribute a  
 WHERE a.attrelid = $oid AND a.attnum > 0 AND NOT a.attisdropped  
 ORDER BY a.attnum  
 ";  
173    
174          $sth = $mdbh->prepare($sql);          # primary key columns
175          $sth->execute() || die;          my @cols_pk = @{$mscheme->cols_pk($table)};
         while(my $row = $sth->fetchrow_hashref()) {  
                 # attname | format_type | attnotnull | atthasdef | attnum  
176    
177                  push @cols,$row->{attname};          # columns to compare (not in primary key)
178            my @cols_cmp = @{$mscheme->cols_notpk($table)};
179    
180            my @cols_skip;  # skipped columns
181            my @cols_test;  # all colums to test (without skipped)
182    
183            foreach my $row (@{$mscheme->pg_attribute($table)}) {
184                    # attname format_type attnotnull atthasdef attnum default references
185    
186                  # FIXME: do something with attributes which shouldn't be compared                  # FIXME: do something with attributes which shouldn't be compared
187                  # (date, time, datetime, timestamp)                  # (date, time, datetime, timestamp)
188                  if ($row->{format_type} =~ /(date)|(time)/i) {                  if ($row->{format_type} =~ /(date)|(time)/i) {
189                          push @cols_skip,$row->{attname};                          push @cols_skip,$row->{attname};
                         next;  
                 }  
   
                 push @cols_test,$row->{attname};  
   
                 if ($row->{attnotnull}) {  
                         push @cols_notnull,$row->{attname};  
190                  } else {                  } else {
191                          push @cols_null,$row->{attname};                          push @cols_test,$row->{attname};
192                  }                  }
193    
194          }          }
         $sth->finish();  
195    
196          if ($debug) {          if ($debug) {
197                  print STDERR "table $table [$oid] not null: (",join(", ",@cols_notnull),")";                  print STDERR "DEBUG: table $table not null cols: (",join(", ",@cols_notnull),")";
198                  print STDERR " - null: (",join(", ",@cols_null),")" if (@cols_null);                  print STDERR " - null cols: (",join(", ",@cols_null),")" if (@cols_null);
199                  print STDERR " - skip: (",join(", ",@cols_skip),")" if (@cols_skip);                  print STDERR " - skip cols: (",join(", ",@cols_skip),")" if (@cols_skip);
200                  print STDERR "\n";                  print STDERR "\n";
201          }          }
202    
203          # diff data          # diff data
204    
         my @cols_pk;    # columns which are primary key  
         my %in_pk;  
   
         $sql="  
 SELECT  
         i.indexrelid as indexrelid, i.indrelid as indrelid,  
         count(a.attname) as cols_in_pk  
 FROM  
         pg_catalog.pg_class c,  
         pg_catalog.pg_index i,  
         pg_catalog.pg_attribute a  
 WHERE  
         c.oid = i.indrelid  
         and i.indisunique  
         and c.relname = '$table'  
         and a.attrelid = i.indexrelid  
 GROUP BY  
         i.indexrelid, i.indrelid, c.relname, i.indisprimary, i.indisunique  
 ORDER BY  
         cols_in_pk ASC, i.indisprimary DESC, i.indisunique DESC, c.relname DESC  
 ";  
         debug_sql($sql);  
         $sth = $mdbh->prepare($sql);  
         $sth->execute() || die;  
         $row = $sth->fetchrow_hashref();  
         if ($row) {  
                 $sql="  
                 select a1.attname as attname from pg_attribute a1, pg_attribute a2 where a1.attrelid = ".$row->{indexrelid}." and a2.attrelid=".$row->{indrelid}." and a1.attname = a2.attname and a2.attnotnull";  
                   
                 debug_sql($sql);  
                 my $sth2 = $mdbh->prepare($sql);  
                 $sth2->execute() || die;  
                 @cols_pk = ();  
                 while (my $row2 = $sth2->fetchrow_hashref()) {  
                         push @cols_pk,$row2->{attname};  
                         $in_pk{$row2->{attname}}++;  
                 }  
                   
         }  
205          if (! @cols_pk) {          if (! @cols_pk) {
206                  print STDERR "can't find PK rows for table '$table' using all\n";                  print STDERR "can't find PK rows for table '$table' using all\n";
207                  @cols_pk = @cols;                  @cols_pk = @cols;
208          }          }
209    
         my @cols_cmp;   # columns to compare  
   
         foreach my $col (@cols_test) {  
                 push @cols_cmp,$col if (! $in_pk{$col});  
         }  
210    
211          if ($verbose) {          if ($verbose) {
212                  print "table '$table' using for key: (",join(", ",@cols_pk),") to compare cols: (",join(", ",@cols_cmp),")\n";                  print "table '$table' using for key: (",join(", ",@cols_pk),") to compare cols: (",join(", ",@cols_cmp),")\n";
# Line 287  ORDER BY Line 241  ORDER BY
241    
242          my $order = sql_order(@cols_pk);          my $order = sql_order(@cols_pk);
243          $msql .= $order;          $msql .= $order;
244          $ssql .= sql_where(@cols_pk) . $order;          $ssql .= $order;
245    
246          debug_sql($msql);          debug_sql($msql);
         debug_sql($ssql);  
247    
248          my $msth = $mdbh->prepare($msql) || die;          my $msth = $mdbh->prepare($msql) || die $mdbh->errstr;
249          $msth->execute() || die;          $msth->execute() || die $msth->errstr;
250    
251          my $ssth = $sdbh->prepare($ssql) || die;          my $ssth = $sdbh->prepare($ssql) || die $sdbh->errstr;
252            $ssth->execute() || die $ssth->errstr;
253    
254          my $diff_row = 0;          my $diff_row = 0;
255    
256          my ($mrow,$srow);          my ($mrow,$srow);
257          my ($have_mrow,$have_srow) = (0,0);          # have_*
258          my @pk_val;          use constant NO_ROW => 0;
259            use constant FETCH_ROW => 1;
260            use constant HAVE_ROW => 2;
261            my ($have_mrow,$have_srow) = (FETCH_ROW,FETCH_ROW);
262    
263            while ($have_mrow != NO_ROW || $have_srow != NO_ROW) {
264    
265                    debug "have mrow: $have_mrow srow: $have_srow\n";
266    
267                    sub pk_val {
268                            my $row = shift || die "need row";
269                            my @cols = shift || die "need cols";
270                            my @val;
271                            foreach my $col (@cols) {
272                                    push @val,$row->{$col};
273                            }
274                            return @val;
275                    }
276    
         my $more_rows = 1;  
         while (!$have_mrow || !$have_srow) {  
277                  # fetch row from master                  # fetch row from master
278                  if (!$have_mrow) {                  if ($have_mrow == FETCH_ROW) {
279                          print "fetch row from master [$more_rows]: $msql\n" if ($debug);                          debug "fetch row from master: $msql\n";
280                          $mrow = $msth->fetchrow_hashref();                          $mrow = $msth->fetchrow_hashref();
281                          debug_row($mrow,@cols);                          debug_row($mrow,@cols);
282    
283                          if ($mrow) {                          if ($mrow) {
284                                  # fill-in primary key values                                  # fill-in primary key values
285                                  @pk_val = ();                                  $have_mrow = HAVE_ROW;
                                 foreach my $col (@cols_pk) {  
                                         push @pk_val,$mrow->{$col};  
                                 }  
                                 $have_mrow = 1;  
286                          } else {                          } else {
287                                  $have_mrow = 0;                                  $have_mrow = NO_ROW;
288                          }                          }
289                  }                  }
290    
291                  # fetch row from slave                  # fetch row from slave
292                  if (!$have_srow) {                  if ($have_srow == FETCH_ROW) {
293                          print "fetch row from slave [$more_rows]: $ssql\n" if ($debug);                          debug "fetch row from slave: $ssql\n";
                         $ssth->execute(@pk_val) || die;  
294                          $srow = $ssth->fetchrow_hashref();                          $srow = $ssth->fetchrow_hashref();
295                          debug_row($srow,@cols);                          debug_row($srow,@cols);
296                          if ($srow) {                          if ($srow) {
297                                  $have_srow = 1;                                  $have_srow = HAVE_ROW;
298                          } else {                          } else {
299                                  $have_srow = 0;                                  $have_srow = NO_ROW;
300                          }                          }
301                  }                  }
302    
303                  # end of this table?                  debug "have mrow: $have_mrow srow: $have_srow\n";
                 if (!$have_mrow && !$have_srow) {  
                         last;  
                 }  
304    
305                  # insert into slave database                  # insert into slave database
306                  sub sql_insert {                  sub sql_insert {
307                            my $dbh = shift @_ || die "need dbh";
308                          my $table = shift @_ || die "need table as argument";                          my $table = shift @_ || die "need table as argument";
309                          my $row = shift @_ || die "need row data";                          my $row = shift @_ || die "need row data";
310                          my @cols = @_;                          my @cols = @_;
# Line 350  ORDER BY Line 312  ORDER BY
312                          my $sql = "insert into $table (".join(",",@cols).") values (";                          my $sql = "insert into $table (".join(",",@cols).") values (";
313                          my $comma = "";                          my $comma = "";
314                          foreach my $col (@cols) {                          foreach my $col (@cols) {
315                                  $sql .= $comma.$mdbh->quote($row->{$col});                                  $sql .= $comma.$dbh->quote($row->{$col});
316                                  $comma = ",";                                  $comma = ",";
317                          }                          }
318                          $sql.=")";                          $sql.=")";
# Line 360  ORDER BY Line 322  ORDER BY
322    
323                  # delete from slave database                  # delete from slave database
324                  sub sql_delete {                  sub sql_delete {
325                            my $dbh = shift @_ || die "need dbh";
326                          my $table = shift @_ || die "need table as argument";                          my $table = shift @_ || die "need table as argument";
327                          my $row = shift @_ || die "need row as argument";                          my $row = shift @_ || die "need row as argument";
328                          my @cols_pk = @_;                          my @cols_pk = @_;
329    
330                          my $where = sql_where(@cols_pk);                          my $where = sql_where(@cols_pk);
331    
332                          my $sql = "delete from $table ";                          my $sql = "delete from $table";
333                          foreach my $col (@cols_pk) {                          foreach my $col (@cols_pk) {
334                                  my $val = $sdbh->quote($row->{$col}) || die "can't find value in row for col $col";                                  my $val = $dbh->quote($row->{$col}) || die "can't find value in row for col $col";
335                                  $where =~ s/\?/$val/;                                  $where =~ s/\?/$val/;
336                          }                          }
337                          $sql .= $where;                          $sql .= $where;
# Line 376  ORDER BY Line 339  ORDER BY
339                          return $sql;                          return $sql;
340                  }                  }
341    
342                    # update row in slave database
343                    sub sql_update {
344                            my $dbh = shift @_ || die "need dbh";
345                            my $table = shift @_ || die "need table as argument";
346                            my $col = shift @_ || die "need col to update";
347                            my $row = shift @_ || die "need row";
348                            my @cols_pk = @_;
349    
350                            my $sql = "update $table set $col=".$dbh->quote($row->{$col});
351                            my $where = sql_where(@cols_pk);
352                            foreach my $col (@cols_pk) {
353                                    my $val = $dbh->quote($row->{$col}) || die "can't find value in row for col $col";
354                                    $where =~ s/\?/$val/;
355                            }
356                            $sql .= $where;
357                            debug_sql($sql);
358                            return $sql;
359                    }
360                  # master        slave                  # master        slave
361                  # 1     =       1       test                  # 1     =       1       test
362                  # 1     <       2       insert mrow                  # 1     <       2       insert mrow
# Line 387  ORDER BY Line 368  ORDER BY
368    
369                  # check key cols for row                  # check key cols for row
370                  foreach my $col (@cols_pk) {                  foreach my $col (@cols_pk) {
371                          if ( (!$mrow && $srow) || ($mrow && $srow && ($mrow->{$col} gt $srow->{$col})) ) {                          if ( ($have_mrow == NO_ROW && $have_srow == HAVE_ROW) ||
372                                    ($have_mrow == HAVE_ROW && $have_srow == HAVE_ROW && $mrow->{$col} gt $srow->{$col}) ) {
373                                  $diff_row++;                                  $diff_row++;
374                                  $pk_same = 0;                                  $pk_same = 0;
375                                  print sql_delete($table,$srow,@cols_pk),"\n";                                  print STDERR "EXTRA row in table '$table' pk: [".join(",",@cols_pk)."] value (".join(",",pk_val($srow,@cols_pk)).")\n" if ($verbose);
376                                  $have_srow = 0; # fetch new slave row                                  print sql_delete($sdbh,$table,$srow,@cols_pk),";\n";
377                                    $have_srow = FETCH_ROW;
378                                  last;                                  last;
379                          } elsif ( ($mrow && !$srow) || ($mrow && $srow && ($mrow->{$col} lt $srow->{$col})) ) {                          } elsif ( ($have_mrow == HAVE_ROW && $have_srow == NO_ROW) ||
380                                    ($have_mrow == HAVE_ROW && $have_srow == HAVE_ROW && $mrow->{$col} lt $srow->{$col}) ) {
381                                  $diff_row++;                                  $diff_row++;
382                                  $pk_same = 0;                                  $pk_same = 0;
383                                  print sql_insert($table,$mrow,@cols),"\n";                                  print STDERR "MISSING row in table '$table' pk: [".join(",",@cols_pk)."] value (".join(",",pk_val($mrow,@cols_pk)).")\n" if ($verbose);
384                                  $have_mrow = 0;                                  print sql_insert($mdbh,$table,$mrow,@cols),";\n";
385                                    $have_mrow = FETCH_ROW;
386                                  last;                                  last;
387                          }                          }
388                  }                  }
389    
390                  if ($pk_same) {                  if ($pk_same && $have_mrow == HAVE_ROW && $have_srow == HAVE_ROW) {
391                          # check non-key cols for row                          # check non-key cols for row
392                          foreach my $col (@cols_cmp) {                          foreach my $col (@cols_cmp) {
393                                  if ($mrow->{$col} ne $srow->{$col}) {                                  if ($mrow->{$col} ne $srow->{$col}) {
394                                          $diff_row++;                                          $diff_row++;
395                                          print STDERR "DIFF in table '$table' row ($col): [".join(",",@pk_val)."] '$mrow->{$col}' != '$srow->{$col}'\n" if ($verbose);                                          print STDERR "DIFF in table '$table' row ($col): [".join(",",@cols_pk)."] '$mrow->{$col}' != '$srow->{$col}'\n" if ($verbose);
396                                          print sql_delete($table,$srow,@cols_pk),"\n";                                          print sql_update($mdbh,$table,$col,$mrow,@cols_pk),";\n";
                                         print sql_insert($table,$mrow,@cols),"\n";  
397                                  }                                  }
398                          }                          }
399                            $have_mrow = FETCH_ROW;
400                            $have_srow = FETCH_ROW;
401                  }                  }
402          }          }
403    
# Line 419  ORDER BY Line 405  ORDER BY
405          $diff_total += $diff_row;          $diff_total += $diff_row;
406  }  }
407    
408  print STDERR "$diff_total differences in all tables\n" if ($verbose && $diff_total > 0);  if ($verbose) {
409            if ($diff_total == 0) {
410                    print STDERR "databases are same\n";
411            } elsif ($diff_total > 0) {
412                    print STDERR "$diff_total differences in all tables\n";
413            } else {
414                    die "this shouldn't happend. please report a bug!";
415            }
416    }
417    
418    # enable triggers again on slave
419    foreach my $tr (@triggers) {
420            print "update pg_trigger set tgenabled = true where tgname='$tr';\n";
421    }
422    # end transaction
423    print "commit;\n";
424    
425  $mdbh->disconnect();  $mdbh->disconnect();
426  $sdbh->disconnect();  $sdbh->disconnect();

Legend:
Removed from v.1.1  
changed lines
  Added in v.1.12

  ViewVC Help
Powered by ViewVC 1.1.26