#!/usr/bin/perl # # pguniqchk.pl - Checks uniqueness of unique keys in tables of a # PostgreSQL database # # Copyright 2002 David D. Kilzer. All rights reserved. # # This program is licensed under the same terms as Perl itself. # use strict; use warnings; use vars qw( $dbh ); # database handle use DBI; use Getopt::Long; my $VERSION = sprintf("%d.%02d", q$Revision: 1.3 $ =~ /(\d+)\.(\d+)/); my $HELP = < \$opt_help, 'host|h=s' => \$opt_host, 'port|p=s' => \$opt_port, 'verbose' => \$opt_verbose, 'version|v' => \$opt_version, ); if ( ! $ret_val || $opt_help || (scalar(@ARGV) < 3 && ! $opt_version) ) { print STDERR $HELP; exit (defined $opt_help ? 0 : 1); } if ($opt_version) { print STDERR "pguniqchk v$VERSION\n"; exit 0; } $db_dsn = 'dbi:Pg:dbname=' . shift @ARGV; $db_user = shift @ARGV; $db_pass = shift @ARGV; if (scalar(@ARGV) > 0) { @db_tables = @ARGV; @ARGV = (); } # # Handle various command-line arguments # if ($opt_host) { $db_dsn .= ';host=' . $opt_host; } if ($opt_port) { $db_dsn .= ';port=' . $opt_port; } # # Connect to database # eval { $::dbh = DBI->connect($db_dsn, $db_user, $db_pass, +{ RaiseError => 1, }, ); }; if ($@) { die "Error connecting to database: $@"; } # # Grab list of tables to check # if (scalar(@db_tables) < 1) { eval { local $::dbh->{RaiseError} = 1 if (! $::dbh->{RaiseError}); my $i = 0; # counter my $sth; # statement handle $sth = $::dbh->prepare(<bind_param(++$i, $db_user); $sth->execute(); while (my $r = $sth->fetchrow_arrayref()) { push(@db_tables, $r->[0]); } $sth->finish(); }; if ($@) { $::dbh->disconnect(); die "Error querying list of tables from database: $@"; } } # # Check uniqueness of unique indices on each table # eval { local $::dbh->{RaiseError} = 1 if (! $::dbh->{RaiseError}); my $found_dups = 0; # Turn off index scans so we may check for duplicate keys print "### Disabling index scans ... " if ($opt_verbose); $::dbh->do(qq{ set ENABLE_INDEXSCAN = off }); print "done\n" if ($opt_verbose); foreach my $tab (@db_tables) { my $i = 0; # counter my $sth; # statement handle $sth = $::dbh->prepare(<bind_param(++$i, $tab); $sth->execute(); while (my $r = $sth->fetchrow_arrayref()) { my $constraint = $r->[1]; my $index_def = $r->[2]; $index_def =~ m/\(([^)]+)\)/; my @cols = map( ${[ split(' ', $_) ]}[0], @{[ split(',', $1) ]} ); my $col = join(', ', @cols); # $index_def above looks like this (on one line): # CREATE UNIQUE INDEX foo_bar_pkey ON foo_bar # USING btree (foo_id int4_ops, bar_id int4_ops) # We then parse out the list of columns in parenthesis into # @cols like this: @cols = qw(foo_id driver_id); # Then we join them into a comma-separated string, $col, for # use with the SQL and status output like this: # $cols = 'foo_id, driver_id'; if ($index_def =~ m/CREATE\s+UNIQUE\s+INDEX/i) { my $sql = < 1 SQL my $sth2 = $::dbh->prepare($sql); $sth2->execute(); if ($sth2->fetchrow_arrayref()) { my $printable_sql = $sql; $printable_sql =~ s/[\s\n]+/ /mg; $printable_sql =~ s/^\s+//; $printable_sql =~ s/\s+$//; print "*** '$constraint' constraint on ($col)\n" . " in '$tab' table is NOT UNIQUE.\n"; print " $printable_sql\n"; while ($sth2->fetchrow_arrayref()) { ; } $found_dups++; } else { print "--- '$constraint' constraint on ($col)\n" . " in '$tab' table is unique.\n" if ($opt_verbose); } $sth2->finish(); } else { print "xxx '$constraint' is not a unique constraint\n" . " on '$tab' table: skipped.\n" if ($opt_verbose); } } $sth->finish(); } if ($found_dups) { print "*** Don't forget to run this SQL before querying the database:\n"; print " set enable_indexscan = off;\n"; } # Turn on index scans again print "### Enabling index scans ... " if ($opt_verbose); $::dbh->do(qq{ set ENABLE_INDEXSCAN = on }); print "done\n" if ($opt_verbose); }; if ($@) { $::dbh->do(qq{ set ENABLE_INDEXSCAN = on }); $::dbh->disconnect(); die "Error checking uniqueness of unique keys in database: $@"; } exit 0;