head	1.3;
access;
symbols;
locks; strict;
comment	@# @;


1.3
date	2001.04.26.16.46.31;	author kmaples;	state dead;
branches;
next	1.2;

1.2
date	2001.04.17.19.20.25;	author kmaples;	state Exp;
branches;
next	1.1;

1.1
date	2001.04.16.23.19.25;	author kmaples;	state Exp;
branches;
next	;


desc
@@


1.3
log
@moved the data validation scripts under the test/ dir.
@
text
@#!/usr/bin/perl -w
# $Id: compare_dbs.pl,v 1.2 2001/04/17 19:20:25 kmaples Exp $

use diagnostics;

# Databases to compare:

my $DB_A = shift(@@ARGV);
my $DB_B = shift(@@ARGV) || $DB_A;

unless($DB_A){
    print "No database specified!\n\n";
    print "Usage: \n";
    print "  compare_dbs.pl {source_db} {target_db}\n";
    print "  compare_dbs.pl {database}\n\n";
    print "Displays the elements present in source and missing\n";
    print "from target, or checks the data in the database against\n";
    print "it's own schema (empty values, enums, etc.).\n\n";
    print "Either source or target may be a 'mysqldump --no-data'\n";
    print "file, in which case the file will be read in and compared,\n";
    print "rather than the database.\n\n";
    
    exit;   
}

use DBI;

# Stuff grabbed from env.sh:

my $DBPW  = $ENV{DATABASE_ADMIN_PASSWORD};
my $DBUSR = $ENV{DATABASE_ADMIN_USER};
my $DBHOST= $ENV{DATABASE_HOST};
my $DBPORT= $ENV{DATABASE_PORT};

unless($DBPW && $DBUSR && $DBHOST && $DBPORT){
    print "Insufficient ENV info to talk to DB.  Try sourcing env.sh.\n";
    exit;   
}

# Here we get a connection to $DB_B, since we'll be comparing 
# it to the schema defined in $DB_A:
my $dbc  = join(':','DBI','mysql',$DB_B, $DBHOST, $DBPORT);
my $CONN = DBI->connect($dbc, $DBUSR, $DBPW);

# Check for a good connection:
unless( $CONN =~ m/HASH/){
    print "No connection to database: $!\n";
    exit(1);
}

my @@db_a_schema = ();
my @@db_b_schema = ();
my $compare_different_dbs = 0;
my $current_table = '';
my %bad_tables = ();
my (%schema_a, %schema_b);

my @@ERR;

my $statement;
my $select;
my $ret;


#print "Getting sqldump of $DB_A ... \n";
@@db_a_schema = get_schema($DB_A);
#print "Parsing $DB_A schema ... \n";
%schema_a = parse_schema_dump(@@db_a_schema);


if($DB_A ne $DB_B){ 
    print "Comparing $DB_A to $DB_B\n";
    $compare_different_dbs = 1;
    
#    print "Getting sqldump of $DB_B ... \n";
    @@db_b_schema = get_schema($DB_B);
#    print "Parsing $DB_B schema ... \n";
    %schema_b = parse_schema_dump(@@db_b_schema);
} else {
    print "Checking data in $DB_A\n";
}

# Perform either the comparison, or the data validatio:
if($compare_different_dbs){
    foreach(keys(%schema_a)){
        my $table = $_;
        
        # Check that there's even such a table there:
        if(!$schema_b{$table}){
            put_error("Table $table missing from $DB_B");
        } else {
            foreach(keys(%{ $schema_a{$table} })){
                my ($col, $def)   = ($_, $schema_a{$table}->{$_});
                my $defb          = $schema_b{$table}->{$_};
                
                my $check_def = 0;
                
                # Check that there's a column there:
                if(!$defb){
                    put_error("Table $table in $DB_B lacks column $col");
                } else {
                    # Check that the defs are the same
                    unless($def eq $defb){
                        put_error("'$table.$col' in $DB_B does not match $DB_A:");
                        put_error("  $DB_A: $def");
                        put_error("  $DB_B: $defb");
                    }
                }
            }
        }
    }
} else {
    while(my ($table, $ref) = (each %schema_a)){
        foreach(keys %{$ref}){
            my ($col, $def) = ($_, $ref->{$_});
            my @@clauses     = ();
            my $enumvals = '';
            
#           print "table: $table\n";
#           print "col:   $col\n";
#           print "def:   $def\n";
            
            # Check for verifiable data:
            if($def =~ m/enum\(([^)]+)\)/i){
                $enumvals = $1;
                push(@@clauses, qq[  $col NOT IN ($enumvals)\n]);
            }   
            if($def =~ m/not\s+null/i){
                push(@@clauses, qq[  $col = NULL\n]);
            }
            # 3. query database ... IF some usable condition applies:
            if(@@clauses){
                $statement = qq[SELECT COUNT($col) FROM $table WHERE \n]; 
                $statement .= join(' OR ', @@clauses);
        
                $select    = $CONN->prepare($statement);
                $ret       = $select->execute;
                $count     = ($select->fetchrow_array)[0];
            
                if($count){
                    $bad_tables{$table} = { $col => $count};    
                }
            }
        }   
    }
}   
# Two routines for errors; these need to be consolidated 
# as usage requires:
if(@@ERR){
    print "The following differences were found:\n\n";
    foreach(@@ERR){ print "  $_\n";} 
}
if(%bad_tables){
    print "The following tables in $DB_A were found to have data\n";
    print "inconsistent with the database's schema:\n\n";
    while(my($k, $v) = each(%bad_tables)){
        print "  table $k:\n";
        while(my($x, $y) = each(%{ $v })){
            print "    column '$x':  $y rows\n";    
        }   
    }   
}

print "\n";

$select->finish() if $select;
$CONN->disconnect;

#---------------------
sub put_error {
#---------------------
    my $err = shift;
    push(@@ERR, $err);   
}
#---------------------
sub get_schema {
#---------------------
    my $dbs   = shift;
    my @@lines = ();
    
    if(-e $dbs){
        open(F, $dbs) or die "Unable to open $dbs for reading: $!\n";
        @@lines = <F>;
        close F;    
    } else {
        @@lines = qx(mysqldump -u$DBUSR -p$DBPW --no-data $dbs );    
    }
    return(@@lines);

}
#---------------------
sub parse_schema_dump {
#---------------------
    # my %schema = parse_schema_dump(@@db_dump);
    # $col_def  = $schema{table}->{col}
    
    my %schema = ();
    my @@dump = @@_;
    my $current_table = '';
    
    while($_ = shift(@@dump)){
        # strip leading and trailing whitespace:
        s/^\s+//;
        s/\s+$//;
        # skip the irrelevant:
        next if m/^#/;
        next if m/^(\s+)?$/;
        
        if(m/^\s*\);\s*$/){ # Table def. termination
            next;   
        }
        if(m/CREATE\s+TABLE\s+(\w+)\s*\(/i){
            $current_table = $1;           
#            print "current table is $current_table\n";
            shift;  # Move on to the next line
        }
        # grab any key info first:
        if(m/^(PRIMARY KEY|(KEY|UNIQUE)\s+\S+)([^,]*),?$/i){
            # Keyname, followed by key def. - e.g.,
            # PRIMARY KEY = (field)
            # KEY name    = (field(s))
            # UNIQUE name = (field(s))
            $schema{$current_table}->{$1} = $3;
            next;
        }
        if(m/^(\w+)\s+(.*),(\s+)?$/){
#           print "  current col: $1\n";
#           print "  current def: $2\n";
            $schema{$current_table}->{$1} = $2;
        }
    } 
    # DEBUG
#    foreach my $key (keys %schema){
#       print "table $key\n";
#       foreach(keys %{$schema{$key}}){
#           my ($n, $v) = ($_ , $schema{$key}->{$_});
#           print "  $_  =  $schema{$key}->{$_}\n"; 
#       }   
#    }       
    return(%schema);
}



__END__



@


1.2
log
@Modified to take filenames or databases as args
@
text
@d2 1
a2 1
# $Id: compare_dbs.pl,v 1.1 2001/04/16 23:19:25 kmaples Exp $
@


1.1
log
@A simple utility script to compare the schema of one db to another, or check
the integrity of data in a db against its schema
@
text
@d2 1
a2 1
# $Id: $
d15 1
a15 1
    print "  compare_dbs.pl {database}\n";
d18 4
a21 1
    print "it's own schema (empty values, enums, etc.).\n";
d66 1
a66 1
@@db_a_schema = qx(mysqldump -u$DBUSR -p$DBPW --no-data $DB_A );
d76 1
a76 1
    @@db_b_schema = qx(mysqldump -u$DBUSR -p$DBPW --no-data $DB_B );
d175 14
d190 1
a190 1

d246 1
@

