# This program is copyright 2011 Percona Ireland Ltd. # Feedback and improvements are welcome. # # THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED # WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF # MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, version 2; OR the Perl Artistic License. On UNIX and similar # systems, you can issue `man perlgpl' or `man perlartistic' to read these # licenses. # # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., 59 Temple # Place, Suite 330, Boston, MA 02111-1307 USA. # ########################################################################### # Schema package # ########################################################################### { # Package: Schema # Schema encapsulates a data structure representing databases and tables. # Although in MySQL "schema" is technically equivalent to "database", we # use "schema" loosely to mean a collection of schema objects: databases, # tables, and columns. These objects are organized in a hash keyed on # database and table names. The hash is called schema and looks like, # (start code) # db1 => { # tbl1 => { # db => 'db1', # tbl => 'tbl1', # tbl_struct => # ddl => "CREATE TABLE `tbl` ( ...", # } # } # (stop code) # Each table has at least a db and tbl key and probably a tbl_struct. # # The important thing about a Schema object is that it should be the only # data structure with this data, and other modules should reference and add # data to it rather than creating other similar copies. does # this for example. # # The other important thing about a Schema object is that the data structure # is the standard. Other modules should take db or tbl hashrefs pointing # into the data structure. Tbl hashrefs should always have at least at db # and tbl key (which is redundant but necessary so that each tbl hashref # includes its own database and table name). # # Schema objects are usually added by a , but you can add # them manually if needed; see . package Schema; use strict; use warnings FATAL => 'all'; use English qw(-no_match_vars); use constant PTDEBUG => $ENV{PTDEBUG} || 0; # Sub: new # # Parameters: # %args - Arguments # # Returns: # Schema object sub new { my ( $class, %args ) = @_; my @required_args = qw(); foreach my $arg ( @required_args ) { die "I need a $arg argument" unless $args{$arg}; } my $self = { %args, schema => {}, # keyed on db->tbl # columns => {}, # No tools use is_duplicate_table() or # tables => {}, # is_duplicate_column() yet... }; return bless $self, $class; } sub get_schema { my ( $self ) = @_; return $self->{schema}; } sub get_table { my ( $self, $db_name, $tbl_name ) = @_; if ( exists $self->{schema}->{$db_name} && exists $self->{schema}->{$db_name}->{$tbl_name} ) { return $self->{schema}->{$db_name}->{$tbl_name}; } return; } #sub is_duplicate_column { # my ( $self, $col ) = @_; # return unless $col; # return ($self->{columns}->{$col} || 0) > 1 ? 1 : 0; #} #sub is_duplicate_table { # my ( $self, $tbl ) = @_; # return unless $tbl; # return ($self->{tables}->{$tbl} || 0) > 1 ? 1 : 0; #} # Sub: add_schema_object # Add a schema object. This sub is called by # . # # Parameters: # $schema_object - Schema object hashref. sub add_schema_object { my ( $self, $schema_object ) = @_; die "I need a schema_object argument" unless $schema_object; my ($db, $tbl) = @{$schema_object}{qw(db tbl)}; if ( !$db || !$tbl ) { warn "No database or table for schema object"; return; } my $tbl_struct = $schema_object->{tbl_struct}; if ( !$tbl_struct ) { warn "No table structure for $db.$tbl"; return; } # Add/save this schema object. $self->{schema}->{lc $db}->{lc $tbl} = $schema_object; # Get duplicate column and table names. # map { $self->{columns}->{lc $_}++ } @{$tbl_struct->{cols}}; # $self->{tables}->{lc $tbl_struct->{name}}++; return; } sub find_column { my ( $self, %args ) = @_; my $ignore = $args{ignore}; my $schema = $self->{schema}; my ($col, $tbl, $db); if ( my $col_name = $args{col_name} ) { ($col, $tbl, $db) = reverse map { s/`//g; $_ } split /[.]/, $col_name; PTDEBUG && _d('Column', $col_name, 'has db', $db, 'tbl', $tbl, 'col', $col); } else { ($col, $tbl, $db) = @args{qw(col tbl db)}; } $db = lc($db || ''); $tbl = lc($tbl || ''); $col = lc($col || ''); if ( !$col ) { PTDEBUG && _d('No column specified or parsed'); return; } PTDEBUG && _d('Finding column', $col, 'in', $db, $tbl); if ( $db && !$schema->{$db} ) { PTDEBUG && _d('Database', $db, 'does not exist'); return; } if ( $db && $tbl && !$schema->{$db}->{$tbl} ) { PTDEBUG && _d('Table', $tbl, 'does not exist in database', $db); return; } my @tbls; my @search_dbs = $db ? ($db) : keys %$schema; DATABASE: foreach my $search_db ( @search_dbs ) { my @search_tbls = $tbl ? ($tbl) : keys %{$schema->{$search_db}}; TABLE: foreach my $search_tbl ( @search_tbls ) { next DATABASE unless exists $schema->{$search_db}->{$search_tbl}; if ( $ignore && grep { $_->{db} eq $search_db && $_->{tbl} eq $search_tbl } @$ignore ) { PTDEBUG && _d('Ignoring', $search_db, $search_tbl, $col); next TABLE; } my $tbl = $schema->{$search_db}->{$search_tbl}; if ( $tbl->{tbl_struct}->{is_col}->{$col} ) { PTDEBUG && _d('Column', $col, 'exists in', $tbl->{db}, $tbl->{tbl}); push @tbls, $tbl; } } } @tbls = sort {$b->{name} cmp $a->{name}} @tbls; return \@tbls; } sub find_table { my ( $self, %args ) = @_; my $ignore = $args{ignore}; my $schema = $self->{schema}; my ($tbl, $db); if ( my $tbl_name = $args{tbl_name} ) { ($tbl, $db) = reverse map { s/`//g; $_ } split /[.]/, $tbl_name; PTDEBUG && _d('Table', $tbl_name, 'has db', $db, 'tbl', $tbl); } else { ($tbl, $db) = @args{qw(tbl db)}; } $db = lc($db || ''); $tbl = lc($tbl || ''); if ( !$tbl ) { PTDEBUG && _d('No table specified or parsed'); return; } PTDEBUG && _d('Finding table', $tbl, 'in', $db); if ( $db && !$schema->{$db} ) { PTDEBUG && _d('Database', $db, 'does not exist'); return; } if ( $db && $tbl && !$schema->{$db}->{$tbl} ) { PTDEBUG && _d('Table', $tbl, 'does not exist in database', $db); return; } my @dbs; my @search_dbs = $db ? ($db) : keys %$schema; DATABASE: foreach my $search_db ( @search_dbs ) { if ( $ignore && grep { $_->{db} eq $search_db } @$ignore ) { PTDEBUG && _d('Ignoring', $search_db); next DATABASE; } if ( exists $schema->{$search_db}->{$tbl} ) { PTDEBUG && _d('Table', $tbl, 'exists in', $search_db); push @dbs, $search_db; } } @dbs = sort @dbs; return \@dbs; } sub _d { my ($package, undef, $line) = caller 0; @_ = map { (my $temp = $_) =~ s/\n/\n# /g; $temp; } map { defined $_ ? $_ : 'undef' } @_; print STDERR "# $package:$line $PID ", join(' ', @_), "\n"; } 1; } # ########################################################################### # End Schema package # ###########################################################################