view Lib/IMPL/SQL/Schema/Traits/Diff.pm @ 250:129e48bb5afb

DOM refactoring ObjectToDOM methods are virtual QueryToDOM uses inflators Fixed transform for the complex values in the ObjectToDOM QueryToDOM doesn't allow to use complex values (HASHes) as values for nodes (overpost problem)
author sergey
date Wed, 07 Nov 2012 04:17:53 +0400
parents 4d0e1962161c
children 77df11605d3a
line wrap: on
line source

package IMPL::SQL::Schema::Traits::Diff;
use strict;
use warnings;
use IMPL::lang qw(:compare :hash is);

use IMPL::SQL::Schema();
use IMPL::SQL::Schema::Traits();

use constant {
    schema_t => typeof IMPL::SQL::Schema # defining a constant is a good style to enable compile checks
};

sub Diff {
    my ($self,$src,$dst) = @_;
    
    die new IMPL::InvalidArgumentException( src => "A valid source schema is required") unless is($src,schema_t);
    die new IMPL::InvalidArgumentException( dst => "A valid desctination schema is requried" ) unless is($src,schema_t);
    
    my %dstTables = map { $_->name, $_ } $dst->GetTables;
    
    my @operations;
    
    foreach my $srcTable ( $src->GetTables) {
        my $dstTable = delete $dstTables{$srcTable->name};
        
        if (not $dstTable) {
            # if a source table doesn't have a corresponding destination table, it should be deleted
            push @operations, new IMPL::SQL::Schema::Traits::DropTable($srcTable->name);
        } else {
            # a source table needs to be updated
            push @operations, $self->_DiffTables($srcTable,$dstTable);
        }
        
    }
    
    foreach my $tbl ( values %dstTables ) {
        push @operations, new IMPL::SQL::Schema::Traits::CreateTable(
            new IMPL::SQL::Schema::Traits::Table(
                $tbl->name,
                [ map _Column2Traits($_), @{$tbl->columns} ],
                [ map _Constraint2Traits($_), $tbl->GetConstraints()],
                $tbl->{tag}
            )
        )
    }
    
    return \@operations;
}

sub _DiffTables {
    my ($self,$src,$dst) = @_;
    
    my @dropConstraints;
    my @createConstraints;
    
    my %srcConstraints = map { $_->name, $_ } $src->GetConstraints();
    my %dstConstraints = map { $_->name, $_ } $dst->GetConstraints();
    
    foreach my $cnSrcName (keys %srcConstraints) {
        if ( my $cnDst = delete $dstConstraints{$cnSrcName} ) {
            unless ( $srcConstraints{$cnSrcName}->SameValue($cnDst) ) {
                push @dropConstraints,
                    new IMPL::SQL::Schema::Traits::AlterTableDropConstraint( $src->name, $cnSrcName );
                push @createConstraints,
                    new IMPL::SQL::Schema::Traits::AlterTableAddConstraint( $dst->name, _Constraint2Traits($cnDst) );
            }
        } else {
            push @dropConstraints,new IMPL::SQL::Schema::Traits::AlterTableDropConstraint( $src->name, $cnSrcName );
        }
    }
    
    foreach my $cnDst (values %dstConstraints) {
        push @createConstraints,
        IMPL::SQL::Schema::Traits::AlterTableAddConstraint->new( $dst->name, _Constraint2Traits($cnDst) );
    }
    
    my @deleteColumns;
    my @addColumns;
    my @updateColumns;
    
    my %dstColumnIndexes = map {
        my $col = $dst->GetColumnAt($_);
        ($col->name, { column => $col, index => $_ })
    } 0 .. $dst->ColumnsCount-1;
    
    my @columns;
    
    # remove old columns, mark for update changed columns
    for( my $i=0; $i < $src->ColumnsCount; $i++) {
        my $colSrc = $src->GetColumnAt($i);
        
        if ( my $infoDst = delete $dstColumnIndexes{$colSrc->name} ) {
            $infoDst->{prevColumn} = $colSrc;
            push @columns,$infoDst;
        } else {
            push @deleteColumns,new IMPL::SQL::Schema::Traits::AlterTableDropColumn($src->name,$colSrc->name);
        }
    }
    
    #insert new columns at specified positions
    foreach ( sort { $a->{index} <=> $b->{index} } values %dstColumnIndexes ) {
        splice(@columns,$_->{index},0,$_);
        push @addColumns, new IMPL::SQL::Schema::Traits::AlterTableAddColumn($src->name, _Column2Traits( $_->{column}, position => $_->{index} ));
    }
    
    # remember old indexes
    for(my $i =0; $i< @columns; $i ++) {
        $columns[$i]->{prevIndex} = $i;
    }
    
    # reorder columns
    @columns = sort { $a->{index} <=> $b->{index} } @columns;
    
    foreach my $info (@columns) {
        if ($info->{prevColumn} && ( !$info->{column}->SameValue($info->{prevColumn}) or $info->{index}!= $info->{prevIndex} ) ) {
            my $op = new IMPL::SQL::Schema::Traits::AlterTableChangeColumn($src->name,$info->{column}->name);

            $op->position( $info->{index} ) unless $info->{prevIndex} == $info->{index};
            $op->isNullable( $info->{column}->isNullable ) unless equals($info->{column}->isNullable,$info->{prevColumn}->isNullable);
            $op->defaultValue( $info->{column}->defaultValue ) unless equals($info->{column}->defaultValue, $info->{prevColumn}->defaultValue);
            
            my $diff = hashDiff($info->{prevColumn}->tag,$info->{column}->tag);
            $op->options($diff) if %$diff;
            
            push @updateColumns, $op;
        }
    }
    
    my @result = (@dropConstraints, @deleteColumns, @addColumns, @updateColumns, @createConstraints); 
    
    return @result;
}

sub _Column2Traits {
    my ($column,%options) = @_;
    
    return new IMPL::SQL::Schema::Traits::Column(
        $column->name,
        $column->type,
        isNullable => $column->isNullable,
        defaultValue => $column->defaultValue,
        tag => $column->tag,
        %options
    );
}

sub _Constraint2Traits {
    my ($constraint) = @_;
    
    my $map = {
        typeof IMPL::SQL::Schema::Constraint::ForeignKey , typeof IMPL::SQL::Schema::Traits::ForeignKey,
        typeof IMPL::SQL::Schema::Constraint::PrimaryKey , typeof IMPL::SQL::Schema::Traits::PrimaryKey,
        typeof IMPL::SQL::Schema::Constraint::Unique , typeof IMPL::SQL::Schema::Traits::Unique,
        typeof IMPL::SQL::Schema::Constraint::Index , typeof IMPL::SQL::Schema::Traits::Index
    };
    
    my $class = $map->{$constraint->typeof} or die new IMPL::Exception("Can't map the constraint",$constraint->typeof);
    
    return $class->new(
        $constraint->name,
        [ map $_->name, $constraint->columns ]
    )
}

1;